在C++中可重用性是通过继承(inheritance)这一机制来实现的。继承是C++的一个重要组成部分。
从已有的类(父类)产生一个新的子类,称为类的派生。派生类特点:
派生类继承了基类的所有数据成员和成员函数,并添加自己特有的数据或函数成员。
一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的。
继承的类型:单继承和多继承
基类名前面有public的称为“公用继承(public inheritance)”。
声明派生类的一般形式为
class 派生类名: [继承方式] 基类名
{
派生类新增加的成员
} ;
(2) 在声明派生类时增加的成员。体现了派生类对基类功能的扩展。
在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
继承设计方法:
- 可以先声明一个基类,在此基类中只提供一些最基本的功能。
- 然后在声明派生类时加入某些具体的功能,形成适用于某一特定应用的派生类。
(2) 在声明派生类时增加的成员。体现了派生类对基类功能的扩展。
在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
继承设计方法:
- 可以先声明一个基类,在此基类中只提供一些最基本的功能。
- 然后在声明派生类时加入某些具体的功能,形成适用于某一特定应用的派生类。
3种继承方式:public(公用的),private(私有的)和protected(保护的)。
不同的继承方式决定了基类成员在派生类中的访问属性。
(1) 公用继承(public inheritance)
基类的公用成员和保护成员在派生类中保持原有访问属性,即在派生类中保持不变,但其私有成员仍为基类私有,派生类不可直接访问,不属于派生类。
(2) 私有继承(private inheritance)
基类的公用成员和保护成员在派生类中成了私有成员,两者皆成派生类私有成员。其私有成员仍为基类私有,派生类不可访问。(3) 受保护的继承(protected inheritance)
基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。总结:
- 不管什么继承方式,私有的是不对外继承;
- 共有继承,除私有属性外,其他在派生类中不变;
- 私有继承,除私有属性外,其他在派生类中成为私有
- 保护继承,除私有属性外,其他在派生类中成为保护
- 注意类的访问属性和继承方式的区别!!
保护成员和私有成员区别: 保护指不能被外界引用,但可以被派生类继承和访问。在类访问时和私有一致。不管什么继承方式,私有的是不对外继承。 -
- 1 公用继承
继承方式为public,称为公用继承,用公用继承方式建立的派生类称为公用派生类(public derived class),其基类称为公用基类(public base class)。
采用公用继承方式时,基类的公用成员和保护成员在派生类中仍然保持其公用成员和保护成员的属性;
基类的私有成员,不属于派生类,只有基类的成员函数可以引用它,派生类中不可访问。如何在派生类中访问基类的私有成员? - 通过基类中公用的方法
-
2 私有继承
继承方式为private,称为私有继承。
- 私有基类的公用成员和保护成员成为派生类中的私有成员,即派生类的成员函数能访问它们,而在派生类外不能访问它们。
- 基类的私有成员不能被派生类继承。
一般不常使用私有继承。继承方式为private,称为私有继承。
- 私有基类的公用成员和保护成员成为派生类中的私有成员,即派生类的成员函数能访问它们,而在派生类外不能访问它们。
- 基类的私有成员不能被派生类继承。
一般不常使用私有继承。 -
3 保护继承
继承方式指定为protected,称为保护继承。
保护继承的特点是: 保护基类的公用成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有。
从类的用户角度来看,保护成员等价于私有成员。但有一点与私有成员不同(在继承时有区别), 保护成员可以被继承,即被派生类的成员函数引用(内部访问),但不能外部访问。保护成员和私有成员不同之处,在于把保护成员的访问范围扩展到派生类中。但只能是内部访问。 -
4 多级派生
-
-
在多级派生的情况下,各成员的访问属性仍按以上原则确定。在实际中,常用的是公用继承。
-
- 5 派生类的构造函数和析构函数(构造?)
因为构造函数和析构函数不能继承,所以要会设计。
(如何设计派生类的构造函数?)
-
- 在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化。
- 即在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。
-
方法: 在执行派生类的构造函数时,调用基类的构造函数。
class Student1: public Student //声明派生类Student1
{public: //派生类的公用部分
Student1(int n,string nam,char s,int a,string ad):Student(n, nam, s)
//在函数体中只对派生类新增的数据成员初始化
{ age=a;
addr=ad;
}
//派生类构造函数,冒号语法
//带所有参数,一部分以冒号方式传给基类构造函数,一部分传给自己的构造函数体。请注意派生类构造函数首行的写法:
Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
派生类构造函数的一般形式为
派生类构造函数名(总参数表列): 基类构造函数名(参数表列)
{派生类中新增数据成员初始化语句}派生类构造函数在类外面定义的方式:
在类体中只写该函数的声明:
Student1(int n,string nam,char s,int a,string ad);
在类的外面定义派生类构造函数:
Student1∷Student1(int n,string nam,char s,int a,string ad) :Student(n,nam,s)
{ age=a;
addr=ad;
}与前面构造函数冒号语法比较:
Box::Box(int h,int w,int len):height(h),width(w),length(len) { }
在冒号后面的是对数据成员的初始化表。
结论:不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。在建立一个对象时,执行构造函数的顺序是:
①派生类构造函数先调用基类构造函数;
②再执行派生类构造函数本身(即派生类构造函数的函数体)。在派生类对象释放时,先执行派生类析构函数~Student1( ),再执行其基类析构函数~Student( )。基类对象和派生类对象的内存空间分布情况:
首地址一致
- 1 公用继承
- 2 有子对象的派生类的构造函数
类的数据成员中还可以包含类对象。
方法:也是冒号语法。
派生类构造函数的任务应该包括3个部分:
(1) 对基类数据成员初始化;
(2) 对子对象数据成员初始化;
(3) 对派生类数据成员初始化。总结:定义派生类构造函数的一般形式:
派生类构造函数名(总参数表列): 基类构造函数名(参数表列),子对象名(参数表列)
{派生类中新增数成员据成员初始化语句}
执行派生类构造函数的顺序是:
① 调用基类构造函数,对基类数据成员初始化;② 调用子对象构造函数,对子对象数据成员初始化;
③ 再执行派生类构造函数本身,对派生类数据成员初始化。
派生类构造函数的总参数表列中的参数,应当包括基类构造函数和子对象的参数表列中的参数。
如果有多个子对象,派生类构造函数的写法依此类推,应列出每一个子对象名及其参数表列。 - 3 多层派生时的构造函数
一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。
请注意基类和两个派生类的构造函数的写法:
基类的构造函数首部:
Student(int n, string nam)
派生类Student1的构造函数首部:
Student1(int n, string nam],int a):Student(n,nam)
派生类Student2的构造函数首部:
Student2(int n, string nam,int a,int s):Student1(n,nam,a)
初始化的顺序是:
① 先初始化基类的数据成员num和name。
② 再初始化Student1的数据成员age。
③ 最后再初始化Student2的数据成员score。 - 5 派生类的析构函数
派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
在派生类中的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。
与构造函数一样:各自干自己的事情!
调用的顺序:与构造函数正好相反,先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。
- 1 声明多重继承的方法(实现?)
class D:public A,private B,protected C
{类D新增加的成员} - 2 多重继承派生类的构造函数(构造?)
与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。
派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数 (参数表列)
{派生类中新增数成员据成员初始化语句}
各基类的排列顺序任意。
派生类构造函数的执行顺序: 先调用基类的构造函数,再执行派生类构造函数的函数体。调用基类构造函数的顺序是按照声明派生类时基类出现的顺序。多重继承出现的问题:
在多重继承时,从不同的基类中会继承一些重复的数据。如果有多个基类,问题会更突出。即出现数据冗余,或二义性。 - 4 虚基类(解决二义性的方法)
-
- 虚基类的作用
-
- 从多个基类中,提取共同部分,形成虚基类。因实际中不存在,故虚之。
C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。
声明虚基类的一般形式为
class 派生类名: virtual 继承方式 基类名
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。需要注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。2.虚基类的初始化(初始化?)如果在虚基类中定义了带参数的构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。规定: 在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
- 从多个基类中,提取共同部分,形成虚基类。因实际中不存在,故虚之。
A a1; //定义基类A对象a1
B b1; //定义类A的公用派生类B的对象b1
a1=b1; //用派生类B对象b1对基类对象a1赋值
在赋值时舍弃派生类自己的成员。实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。