继承概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程
继承格式
继承权限&访问限定符
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概述 |
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性都不变 |
protected继承 | 变为protected成员 | 仍为protected成员 | 不可见 | 基类的非私有成员都成为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类的非私有成员都成为子类的私有成员 |
总结
(1)基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象
(3) protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承意味着is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的
(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)
(5)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
(6)在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承
派生类默认成员函数
继承体系下,派生类中如果没有显示定义这六个成员函数,编译器则会合成这六个默认的成员函数
【派生类对象的构造与析构】
继承体系下派生类和基类构造函数的调用次序
先调用派生类对象的构造函数(在派生类的初始化列表中调用),再调用基类对象的构造函数。
继承体系下派生类和基类析构函数的调用次序
先调用派生类对象的构造函数(在派生类的最后一条有效语句后调用),再调用基类对象的构造函数。
【说明】
基类有非缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表,且派生类必须定义构造函数
基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数
基类定义了带有形参表构造函数,派生类就一定定义构造函数
基类有缺省构造函数,且派生类无构造函数时,编译器会给其生成构造函数(编译器生成的构造函数无形参),并在其初始化列表中给出基类名和参数列表。
继承体系中的作用域
1. 在继承体系中基类和派生类是两个不同作用域
2. 子类和父类中有同名成员(同名函数-->不形成重载--->不在统一作用域),子类成员将屏蔽父类对成员的直接访问(在子类成员函数中,可以使用 基类::基类成员 访问)–隐藏 –重定义
3. 注意在实际中在继承体系里面最好不要定义同名的成员
赋值兼容规则
在public继承权限下,子类和派生类对象之间有:
子类对象可以赋值给父类对象(切割/切片)
父类对象不能赋值给子类对象
父类的指针/引用可以指向子类对象
子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)(但是有隐患(越界访问),所以不建议)
继承体系下派生类的对象模型
对象模型:对象中非静态成员变量在内存中的布局形式。
1. 单继承
1)普通单继承
//普通单继承
// D的大小:B + _d ===> 8
// D d;(编译器没有为派生类合成构造函数)
// D的模型 _b --->B
// _d
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class D:public B
{
public:
int _d;
};
int main()
{
cout << sizeof(B) << endl;//4
cout << sizeof(D) << endl;//8 B + _d ===> 8
D d;//编译器没有为派生类合成构造函数
d._b = 1;
d._d = 2;
return 0;
}
2)虚拟单继承
//虚拟单继承
// D的大小:B + _d ===> 8
// D d;(编译器为派生类合成构造函数)
// D的模型 _b
// _d
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class D :virtual public B
{
public:
int _d;
};
int main()
{
cout << sizeof(B) << endl;//4
cout << sizeof(D) << endl;//12 <=== 4 + B + _d
D d;
d._b = 1;
d._d = 2;
return 0;
}
2. 多继承(继承两个以上的类)
//多继承
// D的大小:B1 + B2 + _d ===> 12
// D d;(编译器没有为派生类合成构造函数)
// D的模型 _b1 --->B1
// _b1 --->B2
// _d
#include<iostream>
using namespace std;
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D:public B1,public B2//第二个public若不写,则默认继承权限 public
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//12
D d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
return 0;
}
3. 菱形继承( 单继承 + 多继承)
//菱形继承
// D的大小:B + C1 + C2 + _d ===> 20
// D d;(编译器没有为派生类合成构造函数)
// D的模型 _b --->B
// _c1 --->C1
// _b --->B
// _c1 --->C2
// _d
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1 :public B
{
public:
int _c1;
};
class C2 :public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//20
D d;
//d._b = 10;//编译失败(二义性)
d.C1::_b = 10;
return 0;
}
4. 虚拟继承(编译器为派生类合成构造函数)
5. 菱形虚拟继承(解决菱形继承的二义性问题)
//菱形虚拟继承
// D的大小: C1(B+_c1) + C2(B+_c2) + _d + B(_b) ===> 24
// D d;(编译器为派生类合成构造函数)
// D的模型 _b --->C1::B
// _c1 --->C1
// _b --->C2::B
// _c2 --->C2
// _d
// _b --->B
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1 :virtual public B
{
public:
int _c1;
};
class C2 :virtual public B
{
public:
int _c2;
};
class D: public C1,public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;//24
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
return 0;
}
【面试题】
1. 什么是继承?
答:继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。
2. 什么是菱形继承?菱形继承有什么缺陷?
答:菱形继承为单继承+多继承。
缺陷:二义性。
3. 什么是菱形虚拟继承?为什么使用菱形虚拟继承可以解决菱形继承存在的问题?
4. 继承体系中,基类那些成员被派生继承了?友元可以继承吗?
答:基类中静态成员变量可以被派生类继承下来。
基类中 有元不可以被派生类被继承。