人终究局限于人的次元,以人的次元来看世界难道不是以人的思维去理解人脑海中的世界?我们终究是在自己的世界中自娱自乐!
人们把看到的、听到的、嗅到的、触到的众多事实描述成世界,这个世界影响着人的性格,造就了千千万万的人。
人们总是在自己的世界中彷徨,不知所终,而终点始终就在前方。
派生类对象存储了基类的数据成员(因为派生类继承了基类的实现);
派生类对象可以使用基类的方法(因为派生类继承了基类的接口)。
类继承的基本规则
派生类不能直接访问基类的私有成员(只有通过基类的公有成员函数访问)。
类继承的构造函数规则:
派生类不能继承基类的构造函数、析构函数和赋值运算符。
派生类不能继承基类的构造函数,但在构造派生类对象之前又必须先构造基类对象,因此派生类的构造函数引入了的成员初始化列表(列表包含基类构造函数),初始化基类对象。
派生类和基类之间的特殊关系
基类引用和指针可以指向派生类。
由派生类到基类的转换称为向上类型转换,是安全的;反之,则不安全。
多态公有继承
使得同一种方法在基类和派生类中的行为是不同的。
有两种机制可以实现多态公有继承:
1. 在派生类中重新定义基类的方法;
2. 使用虚方法(使用关键字virtual)。
虚方法
若变量为基类(声明了虚函数)引用或指针,且指向派生类对象,则允许此变量访问派生类的虚方法;若指向基类对象,则允许访问基类的虚方法。这是一种多态。
如果没有声明虚函数,程序将根据引用或指针类型选择方法。
若变量为基类对象,将派生类对象赋值给该变量(强制向上类型转换),则只会赋给基类的部分。
方法在基类中被声明为虚后,它在派生类中将自动成为虚方法。
最好将析构函数声明为虚(即使基类不需要显式析构函数提供服务),确保释放派生类对象时,按正确的顺序调用析构函数。
重定义虚方法的注意事项:
如果重新定义了虚方法,应确保与原来的原型完全相同;但如果返回的时基类引用或指针,应修改为派生类的引用或指针(返回类型协变)。
如果基类声明被重载了,则应在派生类中重定义所有基类版本,如果只重定义一个版本,其它版本将会被隐藏。
静态联编和动态联编
编译器将函数调用解释为执行特定的函数代码块(函数名联编),一般可以在编译的过程中完成这种联编,称为静态联编;
由于虚函数的存在,编译器必须生成能够在程序运行时选择正确的虚方法的代码,称为动态联编。
总之,编译器对非虚方法采用静态联编,对虚方法采用动态联编。
动态联编使得只需生成基类对象数组,便可以对派生类和基类对象进行操作。
若派生类不需要重定义基类的任何方法,则静态联编效率更高。
虚函数的工作原理
编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。
隐藏成员是一个指向虚函数地址数组的指针(这个数组叫作虚函数表)。
如果派生类重新定义了虚函数,表格将保存新函数的地址,反之保存原始地址。
使用虚函数时,在内存和执行速度方面有一定的成本:
每个对象都将增大,增大量为存储地址的空间;
对于每个类,编译器都创建一个虚函数地址表;
对于每个函数调用,都要执行一项额外的操作,到表中查找地址。
访问控制:protected
private和protected的区别只有在基类派生的类中才会表现出来。派生类中的成员可以直接访问基类的保护成员。
抽象基类(Abstract Base Class)ABC
若干个类的共同特征可以构造成一个抽象基类,但不能创建抽象基类的对象。
有ABC派生出的类有时被称为具体(concrete)类,表示可以创建这些类型的对象。
纯虚函数
C++通过纯虚函数来提供未实现的函数。
纯虚函数声明的结尾处为=0。
要成为真正的ABC,必须至少包含一个纯虚函数。
被声明为纯虚函数的函数仍可以在方法文件中提供函数定义。
继承和动态内存分配
派生类不使用new
此时,不需要为派生类定义显式析构函数、复制构造函数和赋值运算符。
派生类使用new
此时,必须为派生类定义显式析构函数、复制构造函数(通过成员初始化列表来完成基类部分的复制)和赋值运算符(通过显式调用基类赋值运算符来完成基类部分的赋值)。
友元继承
派生类的友元函数可以通过对派生类对象参数进行强制向上类型转换(转换为基类对象),来对基类部分进行操作。
本章介绍的继承:
简单继承
多态继承
抽象继承