面向对象程序设计
核心思想
- 数据抽象
- 继承
- 动态绑定
继承
派生类直接或者间接的继承基类,基类负责定义共有成员,派生类负责定义特有成员。
虚函数:基类希望派生类各自定义适合自己的版本。派生类需要重新定义基类的所有虚函数
- 对于希望直接继承基类的函数:使用普通函数
- 对于希望派生类覆盖的函数:使用虚函数(使用指针或者引用调用虚函数会发生动态绑定)
派生类列表
友元关系不可继承,每个类负责自己的访问权限。
动态绑定
使用基类的引用(或者指针)调用一个虚函数发生动态绑定,应用对象是基类本身或者该基类的派生类会调用不同的函数。
虚函数
- 虚函数可以是除了构造函数外的非静态函数。
- 出现在类内部的声明中,不用于类外部的定义。
- 虚函数在派生类中还是虚函数,可以在派生类中用
virtual
标注一下 - 非虚函数的解析过程发生在编译时,因为没有版本选择的问题。
- 如果派生类没有覆盖基类的虚函数,那这个函数相当于直接继承的普通函数。
- 虚函数必须定义,因为编译器不知道应该使用哪个虚函数。
- 派生类中覆盖的虚函数形参要保持一致,如果型参不一致,编译器不会认为这是覆盖(使用
override
出现这种情况会报错提示);返回值也是,除了返回自己的指针或者引用 返回自己不行吗? - 虚函数默认实参没太看懂
- 回避虚函数的动态绑定机制:使用作用域运算符。
访问权限
派生列表中的访问说明符:public
,private
,protected
作用是控制派生类用户对于基类成员的访问权限
- 访问权限符对派生类内部成员或友元的访问权限没影响,基类是怎样权限就是怎样
-
- 如果权限符是共有的,访问还按基类的权限来
- 如果权限符是私有的,基类成员都是私有
- 如果访问符是保护的,基类成员都是保护
派生类向基类的类型转换
派生类包含的成员对象:继承自基类的对象+派生类定义的对象。
派生类是基类的超集,可以做到基类所做的所有功能。
- 派生类可以直接当做基类使用。
- 基类的引用和指针可以指向派生类发生隐式转换。(实际上是绑定到了派生类上的共有基类部分)
- 基类不能想派生类转换,但是dynamic_cast,static_cast
- 派生类和基类不能通过初始化(调用基类构造函数)或者赋值转化(调用基类赋值运算符),派生类的部分将会被切掉。
如果派生类不是共有继承基类,则不能向基类转换。(P544没太看懂)
派生类的构造函数
每个类都要负责自己的成员初始化过程
派生类的构造函数初始化过程:传递参数给基类的构造函数初始化基类的成员,然后传递参数给自己的构造函数初始化自己的成员。遵循基类的接口
继承和静态成员
静态成员定以后无论怎么继承都是唯一实例,通过基类和派生类都可以调用。
派生类的声明
和其他类差别不大。
声明语句的作用,让程序知晓某个名字和该名字代表的实体
防止类或函数的继承
C++11 final
class NoDerived final{/**/}
void f1(int) const final;
静态类型和动态类型
静态类型:在编译时就知道的类型。
动态类型:在程序运行时才知道,比如动态绑定的对象。
如果没有使用引用或者指针访问虚函数,编译时就会确定版本。(不会发生动态绑定)
纯虚函数
类的内部声明语句=0
可以讲一个虚函数声明为纯虚函数。
抽象基类
含有纯虚函数的类是抽象基类。
抽象基类负责定义接口,不可直接创建
受保护的成员
protected
对用户是私有的,对派生类是公有的
派生类的成员或者友元可以访问派生类的私有函数和基类的受保护成员,但是派生类中普通的基类对象对访问受保护成员没有特权。否则就能通过套用新类钻protected
的空子。
基类的设计
接口分为三部分:
- 共有部分
- 保护部分:仅让派生类访问
- 私有部分:仅让基类内部和基类的友元访问
在派生类中修改成员权限
class D : public A{
public:
using A::pro //修改为public
private:
using A::pub//修改为private
};
class和struct
唯一的区别:默认继承权限不同
struct
:默认公有继承
class
:默认私有
派生类的作用域
派生类的作用域在基类中,可以在派生类中覆盖基类的同名对象或函数(即使形参不一致)
一个对象的静态类型决定了能执行的操作,调用函数时该类型是搜索起点。
名字查找的步骤:
- 首先确定调用函数的静态类型
- 在这个静态类型的类中寻找,找不到依次沿着继承链向上寻找
- 找到后进行类型检查
- 如果调用合法,该函数是虚函数而且是通过引用或指针调用的,根据动态类型选择调用版本,否则常规调用
成员函数无论是否是虚函数都可以被重载。不要虚函数不行吗?将无法实现多态
P511覆盖重载的函数有点没看懂
虚析构函数
将基类析构函数写虚的目的:在delete调用析构函数是多态地调用适合该对象实际类型的虚构函数(无论该析构函数是合成的还是自己定义的)。
P552-P557暂时跳过
继承的构造函数
使用using语句继承构造函数
- 不改变构造函数的访问级别
- 如果基类有两个形参(第二个含有默认实参),则会继承两个版本(一个两个形参,另一个一个形参)
- 如果基类多个构造函数一般都会继承,两个例外
- 派生类定义的构造函数与基类有相同的形参列表
- 拷贝和移动构造函数不被继承