1. 公有派生(公有继承)
基类的公有成员将成为派生类的公有成员
基类的私有部分只能通过基类的公有和保护方法访问
派生类
不能直接访问基类的私有成员
需要在继承特性中添加:
- 派生类的构造函数
- 按需添加额外的数据成员和成员函数
2. 派生类构造函数
派生类构造函数由于无法直接访问基类私有成员,所以
必须使用基类构造函数
- 基类对象首先被创建
- 派生类对象应通过成员初始化器列表将基类信息传递给基类构造函数
- 派生类构造函数应初始化派生类新增的数据成员
由于基类对象首先被创建,如果不调用基类构造函数,程序将使用默认的基类构造函数
除虚基类外,类智能将值传递给相邻的基类
成员初始化器列表只能用于构造函数
3. 派生类与基类的特殊关系
- 派生类对象可以使用基类的方法
- 基类指针可以在不进行显式类型转换的情况下指向派生类对象,但是只能调用基类方法
- 基类引用可以在不进行显式类型转换的情况下引用派生类对象 ,但是只能调用基类方法
- 不可以将基类对象和地址赋给派生类引用和指针
可以将基类对象初始化为派生类对象或将派生对象赋给基类对象,将只复制基类部分
4. 继承的分类
- 公有继承
- 保护继承
- 私有继承
公有继承建立的是is-a关系,is a kind of
5. 多态公有继承
希望同一个方法在派生类和基类中的行为是不同的,这种行为叫做多态
实现多态的两种方法:
- 在派生类中重新定义基类的方法
- 使用虚拟方法
6. virtual
如果方法是由引用或指针调用的,那么使用virtual能够确定使用基类方法还是派生类方法
如果没有virtual, 那么程序将根据引用或指针的类型选择方法,使用virtual后,
程序将根据引用或指针指向的对象的类型来选择方法
虚拟函数的这种行为非常方便,因此经常在基类中将派生类会重新定义的方法声明为虚拟方法
方法在基类中被声明为虚拟的后,它在派生类中将自动成为虚拟方法
为基类声明一个虚析构函数:如果不是虚拟的,则只调用对应于指针类型的析构函数,如果是虚拟的,将调用相应对象类型的析构函数。因此使用虚拟析构函数可以确保正确的析构函数序列被调用
关键词virtual只用于类声明的方法原型中
7. 静态联编和动态联编
C++中,由于函数重载,编译器必须查看函数参数以及函数名才能确定使用哪个函数,编译器可以在编译过程中完成这种联编,在编译过程中进行的联编叫做静态联编。
使用虚拟函数后,使用哪一个函数是不能再编译时确定的,编译器必须声称能够在程序运行时选择正确的虚拟方法的代码,称为动态联编。
8. 指针和引用的类型兼容性
将派生类引用或指针转换为基类引用或指针称为向上强制转换,不需要进行显式类型转换
将基类引用或指针转换为派生类引用或指针称为向下强制转换,必须使用显式类型转换
9. 为什么有两种类型的联编
(1)效率
C++的指导原则是不要为不使用的特性付出代价。动态联编增加了额外的处理开销,静态联编效率高
(2) 概念模型
在设计类时,可能包含一些不在派生类中重新定义的成员函数,则不将这些函数设置为虚拟函数
10. 虚拟函数工作原理
工作原理:
为每个对象添加了一个隐藏成员,保存了指向虚拟函数表的指针。调用虚拟函数时,程序查看存储在对象中的表地址,然后转向相应的函数地址表
成本:
- 每个对象都增加一个存储地址的空间
- 编译器对每个类创建一个虚拟函数地址表
- 每个函数调用都需要执行一步额外的操作,即到表中查找地址
注意事项:
- 构造函数不能是虚拟函数
- 析构函数应该是虚拟函数
- 友元不能是虚拟函数
- 在派生类中重新定义继承的方法并不是重载,会隐藏同名的基类方法
11. protected
对于外部世界来说,protected与private类似
对于派生来来说,protected与public类似
最好对类数据成员采用私有访问控制,不要使用保护访问控制。
12. 抽象基类
纯虚函数
virtual type func() = 0; |
13. 继承和动态内存分配
基类使用了动态内存分配,并重新定义了赋值和复制构造函数,那么派生类:
(1) 派生类不使用new
派生类不需要定义显式析构函数、复制构造函数和赋值操作符。
- 派生类使用基类的复制构造函数来复制派生类对象中基类的部分。
- 派生类的默认赋值操作符将自动使用基类的赋值操作符来对基类组件进行赋值
(2) 派生类使用new
必须为派生类定义显式析构函数、复制构造函数和赋值操作符。
- 派生类析构函数自动调用基类的析构函数,故其自身的职责是对派生类构造函数执行的工作进行清理
- 派生类复制构造函数只能访问派生类的数据,因此必须调用基类复制构造函数来处理基类的数据部分,通过成员初始化器列表的方式
- 派生类的赋值操作符只能直接访问派生类的数据,然而必须负责所有的包括基类对象的赋值,所以可以通过显式调用基类复制操作符的方式
14. 传递对象与传递引用
编写使用对象作为参数的函数时,应按引用而不是按值传递
原因:(1)提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。复制大型对象比传递引用花费的时间要多得多
(2)在继承使用虚拟函数时,被定义为接受基类引用参数的函数可以接受派生类
15. 返回对象和返回引用
如果可以不返回对象,则应返回引用
原因:返回对象涉及到生成返回对象的临时拷贝,返回对象的时间成本包括调用复制构造函数来生成拷贝所需要的时间和调用析构函数删除拷贝所需要的时间。返回引用可以节省时间和内存
注意:并不总是可以返回引用,函数不能返回在函数中创建的临时对象的引用。
16. 公有继承的考虑因素
- 构造函数和析构函数不能被继承
- 赋值操作符不能被继承。不过,如果对象属于派生类,那么编译器将使用基类赋值操作符来处理派生对象中基类部分的赋值。