第十五章 面向对象程序设计
- 面向对象程序设计基于三个基本概念:数据抽象,继承,动态绑定
- 基类将类型相关的函数与派生类不做改变直接继承的函数区分对待。对于某些函数,基类希望它的派生类各自定义适合自己的版本,基类就将这些函数声明为虚函数
- 派生类必须在其内部对所有重新定义的虚函数进行声明
- 派生类显式地注明它将使用哪个成员函数改写基类的虚函数,具体措施是在该函数的形参列表之后增加一个override关键字
- 函数的运行版本由实参决定,即在运行时选择函数的版本,所以动态绑定又被成为运行时绑定
- 当我们使用基类的引用(或者指针)调用一个虚函数时发生动态绑定
- 基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
- 任何构造函数之外的非静态函数都可以是虚函数
- 关键字virtual只能出现在类内部的函数声明而不能出现在类外部的函数定义
- 如果一个基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数
- 成员函数如果没被声明为虚函数,则其解析过程发生在编译
- 派生类必须使用基类的构造函数来初始化它的基类部分
- 每个类控制它自己的成员初始化过程
- 首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员
- 如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出多少个派生类,对于每个静态成员来说都只存在一个实例
- 派生类的声明包含类名但是不能包含它的派生列表
- 为了防止继承的发生,可以在类名后跟一个关键字final
- 我们可以将一个派生类对象的指针存储在一个基类的智能指针内
- 有派生类向基类的类型转换,不存在基类向派生类的隐式类型转换
- 对象之间不存在类型转换
- 从派生类向基类的转换只对指针或者引用有效
- 当我们使用基类的指针或引用调用虚函数时就会发生动态绑定
- 所有虚函数都必须有定义
- 当且仅当通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同
虚函数 - 使用override来说明派生类覆盖了基类中的虚函数
- 使用final后,不能再override,也就是不能再覆盖该虚函数
- 如果虚函数使用默认实参,则基类与派生类中定义的默认实参最好一致
- 回避虚函数的机制:使用作用域运算符。只有成员函数(或友元)的代码才需要这个机制
- 通常当一个派生类的虚函数调用它覆盖的基类的虚函数时,我们需要使用回避虚函数的默认机制。否则在运行时,该调用将被解析为对派生类版本自身的调用,从而导致无限递归
抽象基类 - 纯虚函数,在声明语句的分号前加上=0,比如:virtual double func(int) const = 0;其中,=0只能出现在类内部的虚函数声明处
- 含有纯虚函数的类是抽象基类,负责定义接口,不能直接创建一个抽象基类的对象
- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员
- 假定D继承自B:只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;否则,不能转换
- 不论D如何继承自B,D的成员函数和友元都能使用派生类向基类的转换
- 如果D公有或保护地继承自B,则D的派生类的成员或友元可以使用D向B的转换P544
- 友元关系不能传递也不能继承
- 改变个别成员的可访问性,可使用using声明
- 派生类只能为那些它可以访问的名字提供using声明P546
- 派生类的作用域位于基类作用域之内
- 派生类的成员将隐藏同名的基类成员
- 可通过作用域运算符来使用隐藏的成员
- 除了覆盖继承而来的虚函数外,派生类最好不要重用定义在基类中的名字
- 名字查找优先于类型检查
- 声明在内层作用域的函数并不会重载声明在外层作用域的函数
- 基类和派生类的虚函数必须有相同的形参列表,否则,我们无法通过基类的指针或引用来调用派生类的虚函数