2009-7-28 1
第 11章继承和派生类
2009-7-28 2
继承问题的提出?一个类的子类,能否通过原来的类来简化程序设计,提高软件的重用性,并使软件更容易维护。
基本概念
类 A继承了类 B 定义类 A时使用了已定义类 B的部分或全部成员。类 B称为 基类 或 父类,类 A
称为 派生类 或 子类 。
类树 派生类作为另一个类的基类,基类再派生出若干派生类,即构成类树。
2009-7-28 3
如关于雇员和车辆的层次关系:
Teacher(教师 ) Officer(行政 ) Worker(工人 )
Employee(雇员 )
Car(汽车 ) Sportscar(赛车 )
Vehicle(车辆 )
2009-7-28 4
对派生类可作如下几个工作:
1.全部或部分地继承基类的成员数据或成员函数;
2.增加新的成员变量;
3.增加新的成员函数;
4.重新定义已有的成员函数;
5.改变现有的成员属性。
C++中两种继承,单一继承 和 多重继承 。如下图所示。
在校人员类学生类 职工类学生类 职工类在校人员类单一继承 多重继承
2009-7-28 5
派生类定义方式?
class 派生类类名,<Access>基类类名
{ public:
… //公有成员说明
protected:
… //保护成员说明
private:
… //私有成员说明
};
单一继承
2009-7-28 6
注:
1.Access可选,为基类中的成员在派生类中规定访问权限。
可选 public,private,protected之一。缺省时约定为
public。
2,Access为 public时,派生类称为 公有派生 ;为 private
时,称 私有派生 ;为 protected时,称 保护派生 。
2009-7-28 7
公有派生例 1,公有派生。
1101执行后输出:
x=1 y=2 z=3
x=1 y=2 z=3
Sum=15
y=2 z=3
2009-7-28 8
注,1.当一个类从一个基类公有派生时,基类中的公有成员在类外可以通过派生类的对象名与成员名一起来直接使用它。
2.基类中的保护成员只能在派生类的成员函数中直接使用,而在派生类之外不能直接使用。
3.派生类中定义的成员函数不能直接使用基类中的私有成员。
2009-7-28 9
私有派生
1.私有派生类的基类中公有成员和保护成员均为私有的,派生类中可直接使用。
2.派生类外不可直接使用基类中的公有或私有成员
,必须通过派生类中的公有成员函数间接地使用。
3.基类中的私有成员只能通过基类的公有或保护成员函数间接使用它们。
例 2,私有派生。
1102
一般使用公有派生,私有派生及保护派生使用较少。
2009-7-28 10
派生类对基类成员的访问规则
3,外部访问:由友元、继承类函数等访问。
访问有三种类型:
1,内部访问:由类自己的函数访问,如前例类 vehicle 中的函数 print
void print( ) {
cout << "\n weight:" << weight << "pds";
cout << "\n top speed:" << topspeed << "mph";
cout << "\n price,$ "<< price; }
2,类的对象访问:由类创建的对象访问。
vehicle av(15000,60,300000); //创建对象
av.print( ); //调用基类函数
2009-7-28 11
公有派生类对基类成员的访问规则
1,基类的私有数据。
内部访问?OK
对象访问?NON
外部访问?友元 OK,继承类 NON
2,基类的保护数据。
内部访问?OK
对象访问?NON
外部访问?友元 OK,继承类 OK
3,基类的公有数据。
内部访问?OK
对象访问?OK
外部访问?友元 OK,继承类 OK
2009-7-28 12
继承基类的访问权限派生方式 基类中的访问权限基类成员在派生类中的访问权限派生类之外的函数能否访问基类中的成员public public public 可访问
public protected protected 不可访问
public private 不可访问 不可访问
private public private 不可访问
private protected private 不可访问
private private 不可访问 不可访问
2009-7-28 13
抽象类和保护的成员函数若定义的一个类只能用作基类来派生出新类,而不能用作定义对象,该类称为 抽象类 。
当对某些特殊的对象要进行很好地封装时要定义抽象类。
当将一个类的构造函数或析构函数的访问权限定义为保护时,该类为抽象类。
当用抽象类作为基类产生派生类时,在派生类中可调用基类的保护成员。
2009-7-28 14
多个基类派生一个类时的定义方式?
class 派生类类名,<Access>基类类名 1,…,<Access>
基类类名 n
{ public:
… //公有成员说明
protected:
… //保护成员说明
private:
… //私有成员说明
};
派生类类名继承了类名 1~n的所有成员数据和成员函数例 4,多重继承。
1103
多重继承
2009-7-28 15
初始化基类成员派生类的构造函数的一般格式为:
类名,:类名 (形参表 ):基类 1(形参表 1),…,基类 n(
形参表 n)
{…} //初始化派生类中的其他成员数据一般调用基类的构造函数来初始化派生类中的基类成员是由派生类的构造函数来确定。
问题?派生类 的构造函数的定义?
形参表是带说明的调用基类的构造函数中的实参表中的每个参数可是表达式,只与派生类的构造函数中的参数名有关,与顺序无关冒号后列举的要调用基类构造函数的列表称为 初始化列表
2009-7-28 16
注,初始化列表中的某一个构造函数的实参表为空时,则该基类的构造函数的调用可从初始化成员列表中删除。
编译器调用构造函数的次序,基类 成员对象 派生类编译器调用析构函数的次序,基类 成员对象 派生类例 5,输出派生类中构造函数与析构函数的调用关系。
1104执行后输出:
调用基类 1的构造函数!
调用基类 2的构造函数!
调用派生类的构造函数!
调用派生类的析构函数!
调用基类 2的析构函数!
调用基类 1的析构函数!
2009-7-28 17
例 6,派生类中包含对象成员。
1105执行后输出:
调用基类 1的构造函数!
调用基类 2的构造函数!
调用基类 1的构造函数!
调用基类 1的构造函数!
调用派生类的构造函数!
调用派生类的析构函数!
调用基类 1的析构函数!
调用基类 1的析构函数!
调用基类 2的析构函数!
调用基类 1的析构函数!
注意:在派生类的构造函数的初始化成员列表中,对对象成员的初始化必须使用对象名,而对基类成员的初始化使用的是对应基类的构造函数名。
2009-7-28 18
例 7,定义描述学生和职工的类,并实现测试和简单的输入 /输出。
1106继承性可重复使用,以防止程序代码和数据结构的重复设计和编写;
继承使程序更容易理解和维护。
2009-7-28 19
注:
1.派生类的构造函数自动执行基类的构造函数,
且基类的构造函数先执行。
2.派生类的构造函数可不显式地写出基类的构造函数,此时调用基类的无参数构造函数,但若要传递参数,必须写出。如:
car(int aw,int ats,float ap,int anc,int ahp)
:vehicle(aw,ats,ap),numbercylinders(anc),
horsepower(ahp) { }
创建对象:
vehicle av(15000,60,300000);
car ac(3500,100,12000,6,120);
2009-7-28 20
冲突、支配规则和赋值兼容性冲突若一个公有派生类是由两个或多个基类派生,
当基类中成员的访问权限为 public,且不同基类中的成员具有相同的名字时,出现了重名。这时派生类使用到基类中的同名成员时,出现了不唯一性,称为发生了 冲突 。
例 9,程序中含有冲突示例。
1107
2009-7-28 21
三种解决冲突的方法:
1.使得各基类中的成员名各不相同;
2.在各个基类中均将成员数据的访问权限说明为私有的,并在相应的基类中提供成员函数对这些成员数据进行操作;
3.使用作用域运算符来限定所访问成员属于哪一个基类。
一般格式,类名,:成员名例 10,用作用域运算符来确定所访问的成员。
1108将派生类作为基类又派生出新类时,这种限定作用域的运算符不能嵌套使用。如:
类名 1::类名 2::…,:成员名
//错误
2009-7-28 22
例 11,多重继承中的冲突。
1109支配规则派生类中新增加的成员名与基类的成员名允许相同,当没有使用作用域运算符时,派生类中定义的成员名优先于基类中的成员名,这种优先关系称为 支配规则 。
例 12,支配规则示例。
1110
2009-7-28 23
基类和对象成员基类在派生类中只能被继承一次,否则造成成员名的冲突。如:
class A{
public,float x;

};
class B,public A,public A{ //错误

};
可改成:
class B{
A a1,a2; //或 A a[2];

};
2009-7-28 24
类作为派生类的基类与类的对象作为一个类的成员在使用上的区别:
派生类中可直接使用基类的成员,但对象成员的使用必须在对象名后加成员运算符,,”和成员名。
例 13,基类成员与对象成员在使用上的差别。
1111赋值兼容规则公有派生类可将派生类的对象赋给其基类的对象,反之不可。
2009-7-28 25
如,class A{
public:
int x;

};
class B{
public:
int y;

};
class C,public A,public B{
public:
int y;

};
C c1,c2,c3;
A a1,*pa1;
B b1,*pb1;
注,1.派生类的对象可以赋给基类的对象。如:
a1=c1; //将 c1中从类 A中继承来的对应成员分别赋给 a1的对应成员又如:
b1=c1; //将 c1中从类 B中继承来的对应成员分别赋给 b1的对应成员
2009-7-28 26
2.不能将基类的对象赋给派生类对象。如:
c2=a1; c3=b1; //错误
3.还可将一个派生类对象的地址赋给基类的指针变量。如:
pa1=&c2; pb1=&c3;
4.派生类对象可以还初始化基类的引用。如:
B &rb=c1;
后两种在使用基类的指针或引用时只能访问从相应基类中继承来的成员,而不允许访问其他基类的成员或在派生类中增加的成员。
2009-7-28 27
虚基类问题的提出?单一继承中,基类只有一个,
如果一个类由多个基类继承而来,该如何定义?
teacher(教师 ) sudentr(学生 )
data_rec(信息记录 )
t_postgra(在职研究生 )
Postgra(研究生 )
例如学校人员分类的层次关系:
2009-7-28 28
问题?,teacher 和 student都是 data_rec的继承类,
它们都有基类的所有成员,即有一个基类成员的拷贝。 t_postgra分别是 teacher 和 student的继承类,
所以,它具有两者的一个拷贝,即有两个 data_rec
的拷贝 ==,数据冗余解决的办法?,虚基类对多继承的派生类,它将只保持一个基类的成员拷贝。
实现办法?,在继承的基类前加关键字 virtual
class 类名,virtual <access> 基类名 {…} ;
或 class 类名,<access> virtual 基类名 {…} ;
2009-7-28 29
例 14,一个公共的基类在派生类中产生两个拷贝。
1112执行后输出:
x=200 y=100
x=400 z=300
m=500
基类 A 基类 A
基类 B 基类 C
类 D
派生类中包含同一基类的两个拷贝
2009-7-28 30
例 15,派生类中包含同一基类的两个拷贝,产生使用上的冲突。
1113
例 16,定义虚基类,使派生类中只有基类的一个拷贝。 1114
执行后输出:
x=0 y=100
x=0 z=300
m=500
x=400
y=100
x=400
z=300
m=500
基类 A
类 D
基类 B 基类 C
派生类中包含同一基类的两个拷贝
2009-7-28 31
#define null 0
#include <iostream.h>
#include <string.h>
class data_rec {
public:
data_rec( ) {
name=null; }
void insert_name( char *na) {
delete [ ] name;
name=new char[strlen(na)+1];
strcpy(name,na); }
void print ( ) { cout << "\n name:" << name; }
private:
char *name; };
例 17,学校人员分类描述,有类 data_rec,teacher,
student,postgra,tpost。文件为 univ.h。
2009-7-28 32
//定义派生类 student
class student:virtual public data_rec{
public:
student( ):data_rec( ) { id=0; } //构造函数
void insert_id(int pid) { id=pid;}
void print ( )
{
data_rec::print( ); //访问基类的函数
cout << "\n id=" << id;
}private:int id;
};
2009-7-28 33
//定义派生类 teacher
class teacher:virtual public data_rec{
public:
teacher( ):data_rec( ) { //构造函数
sal=0;}
void insert_sal(float psal) {
sal=psal;}
void print ( ){
data_rec::print( ); //访问基类的函数
cout << "\n sal=" << sal;
}private:float sal;
};
2009-7-28 34
//定义派生类 postgra
class postgra:public student
{ public:
postgra( ):student( ) {dn=null;} //构造函数
void insert_dn(char *p)
{ delete [ ] dn;
dn=new char[strlen(p)+1];
strcpy(dn,p); }
void print ( )
{ data_rec::print( ); //访问基类的函数
cout << "\n dept name=" << dn; }private:char *dn;
};
2009-7-28 35
//定义派生类 tpost
class tpost,public teacher,public postgra
{ public:
tpost( ):teacher( ),postgra( ) { } //构造函数
void print ( )
{ teacher::print( ); //访问基类的函数
postgra::print( ); } };
2009-7-28 36
//应用程序
#include univ.h
void main( )
{ teacher tobj; //定义类 teacher 的对象
tpost tpobj; //定义类 tpost 的对象
tobj.insert_name(“Liming”); //访问 data_rec的函数
tobj.insert_sal(1000); //访问 teacher自己的函数
tpobj.insert_name(“Xuli”); //访问 date_rec的函数
tpobj.insert_id(99410); //访问类 student 的函数
tpobj.insert_dn(Comp); //访问类 postgra的函数
tobj.print( ); //访问派生类自己的函数
tpobj.print( ); //访问派生类自己的函数
}
2009-7-28 37
结果,name=Liming
sal= 1000
name= Xuli
sal=0
id= 99410
dept name=Comp
用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在派生的每个派生类的构造函数的初始化成员列表中都必须有对虚基类构造函数的调用。
1115
2009-7-28 38
课 堂 总 结
2 有三种派生类型:公有、保护和私有。
对基类成员的访问有一定的规则。
1 基类和派生类体现了类之间的层次关系。
派生类具有基类的所有成员。
3 派生类的构造函数自动执行基类的构造函数,且基类的构造函数先执行。基类构造函数的参数由派生类传递。
4 继承类具有基类的所有成员,并可对已有的成员进行重新定义。在派生类中访问基类的函数用,:表明。
5 可定义多重派生类。且可利用虚基类使派生类只保持一个基类的成员拷贝。