- 继承方式 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 class
public下,base class
的protected属性被继承到derived class
protected下,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
的成员,