- 继承方式 check
- 隐藏 check
- 多继承
- 多重继承
- 虚继承
定义类
定义一个base class(父类,基类)(虚析构函数),和一个derived class(子类,派生类):
1 | class Person{ |
derived class析构函数的虚拟性继承于base class,所以也是virtual。
一个derived class继承base class,那么derived class对象在内存中,既有derived class自身定义的成员属性m_iSalary,又有从base class继承来的成员属性m_strName。
拷贝所继承的内容到内存。
实例化对象
在堆中实例化derived class对象,当使用子类指针指向子类对象时:
1 | int main(){ |
在栈中实例化derived class对象时:
1 | int main() { |
两种实例化方式输出相同:
1 | Person() |
这说明了,实例化一个子类,必然先实例化一个父类。当内存中有了base class对象,其内容就可以被derived class继承下来。销毁对象时,先执行derived class析构函数,后执行base class析构函数。逆序。
如果在栈中实例化父类对象:
1 | Person person; |
输出:
1 | Person() |
在堆中实例化base class对象:
1 | Person *p = new Person(); |
输出:
1 | Person() |
由结果得出,与代码执行与derived class无关。
但是,当使用父类指针指向子类对象时:
1 | Person *p = new Worker(); |
输出:
1 | Person() |
即这种情况也执行了子类的析构函数。因为定义了父类和子类析构函数为虚析构函数
但是如果没有virtual修饰父子类的析构函数,那么derived class的析构函数不会被执行。这造成了一个“局部销毁”现象。会造成资源泄露,数据结构被破坏。(Effective C++ 中的条款7)。
继承方式
公有继承:

base class的public属性被继承到derived classpublic下,base class的protected属性被继承到derived classprotected下,base class的private属性被继承到derived class不可见位置,即没有被继承。
保护继承:

base class的public,protected成员都被继承到derived class的protected下,
而base class的private成员不被继承。
私有继承:

base class的public,protected成员被继承到derived class的private下,
而base class的private不被继承。
隐藏
给上述base class,derived class增加一个同名非虚函数print():
1 | class Person{ |
代码只是实验用
如果main中:
1 | Worker w; |
输出内容:
1 | Person() |
只执行子类同名函数,base class同名函数被隐藏。
但是,也是可以强制调试用base class同名函数:
1 | Worker w; |
同样的,如果main中用父类指针指向子类对象:
1 | Person *p = new Worker(); // `base class`指针指向`derived class`对象 |
输出内容:
1 | Person() |
因为p是base class指针,所以调用base class的同名函数。但是很多时候是用base class指针指向derived class对象,此时我希望指针指向哪个derived class,就调用哪个derived class的同名函数。要想实现这个功能,只需在base class的同名函数前用virtual修饰。virtual的目的是允许derived class的同名函数的实现客制化。
所以实际中要这样定义:
1 | class Person{ |
is-A
解释了为什么通常这样写:Person *p = new Worker();,而不是Worker *p = new Worker();
有两个类:base class“人”,derived class“亚洲人”。derived class继承base class,此时称“亚洲人”is-A”人“。实例如下:
1 | int main(){ |
这是正确地:
例中,先实例化一个Asian对象a1,后实例化一个Person对象p1 用a1初始化p1,可以表达Asian is-A Person。即derived class对象可以复制给base class。或说base class指针可以指向derived class对象。
但当关系反过来就不对了:
1 | int main(){ |
Person is-A Asian显然就不对了。
所以有了这条规律:derived class对象可以复制给base class;base class指针可以指向derived class对象
这样就可以将父类的对象,或父类的指针,或父类的引用作为函数参数,来使得该函数可以接收子类的对象或父类对象。(这也说明了为什么之前总是使用base class指针指向derived class对象,不使用derived class指针就是防止derived class指针指向base class对象),实例:
1 | void func1(Person *p){} |
当分别有一个base class对象和一个derived class对象,下面的调用没有违反上述规律或Is-A规律:
1 | int main(){ |
如func1()的参数是base class指针,所以既可以指向base class对象func1(&p1),又可以指向derived class对象func1(&a1)。三个函数都可以传入base class或derived class的相关实参。
所以通常将函数参数类型指明为base class,避免错误。
此外,func3()中传入的是Person对象的拷贝(先创建后拷贝),而func1(),fucn2()的参数是指针和引用,不产生临时变量。
截断
如果一个对象类型是base class,那么它只能访问base class的成员,这个现象称为截断:
将子类对象赋值给父类对象,即用子类对象初始化父类变量:
由于
derived class中有从base class继承过来的成员,当用derived class对象初始化base class变量时,derived class中继承来的成员会赋值给base class对象中对应的成员,而derived class自身的成员被截断。原因是,base class变量只能接收自身拥有的成员的数据,假如Worker类继承Person类。分别实例化两个对象,当用
derived class对象初始化base class对象:1
2
3Worker w;
Person p;
p = w;两个对象在内存中的存储如下:

Worker的m_name赋值给Person的m_name,而且p只能调用函数func1()。p只能访问base class成员。用父类指针指向子类对象:
base class指针也只能够访问到base class所拥有的数据成员。而无法访问到derived class自身的数据成员,derived class成员变量被截断。当使用
base class指针指向derived class对象:1
Person p = new Worker("Woston", 100);

同样地,
Worker的m_name赋值给Person的m_name,而且p只能调用函数func1()。p只能访问base class成员。
敲黑板
is-A的逻辑不能错。- 如果一个对象类型是
base class,那么它只能访问base class的成员,