1. 语法
原则:
is-a
父类/子类
基类/派生类语法
class 派生类 : [访问限定符] 基类 {
成员
}
如果不写继承访问限定符,默认是private
实例:图形继承
2. 成员的访问权限
public |
protected |
private |
|
---|---|---|---|
类成员函数 | √ | √ | √ |
友元函数 | √ | √ | √ |
子类函数 | √ | √ | × |
类对象 | √ | × | × |
子类继承了父类所有的成元变量和成员函数。与访问限定符无关。访问限定符只是限制了访问。
子类访问父类成员变量,把父类成员变量访问限制符,改为protected
。
- 继承访问权限变化
分为子类内部和子类对象两种访问方式。
子类内部访问public
继承的父类成员变量
class Base {
public:
int public_data;
protected:
int protected_data;
private:
int private_data;
};
class Derive:public Base {
public:
void test() {
cout<< public_data <
子类内部访问public
继承的父类成员函数
#include
using std::cout;
using std::endl;
class Base {
public:
void public_func(){};
protected:
void protected_func(){};
private:
void private_func(){};
};
class Derive:public Base {
public:
void test() {
public_func();
protected_func();
private_func();
}
};
- 子类内部访问父类成员
public |
protected |
private |
|
---|---|---|---|
public 继承 | √ | √ | × |
protected 继承 | √ | √ | × |
private 继承 | √ | √ | × |
子类内部访问父类成员,只能访问
public
和protected
成员。
- 子类对象访问父类成员
public |
protected |
private |
|
---|---|---|---|
public 继承 | √ | × | × |
protected 继承 | × | × | × |
private 继承 | × | × | × |
子类只有
public
继承父类的时候,才能访问父类的public
成员,其他都不能访问。
通常子类使用public
继承父类。
子类对象访问父类成员访问限定符的变化
继承方式\父类成员 | public |
protected |
private |
---|---|---|---|
public 继承 | public |
protected |
不可见 |
protected 继承 | protected |
protected |
不可见 |
private 继承 | private |
private |
不可见 |
小结
-
public
无论类内部还是类对象都可以访问。 -
protected
类对象不可访问,类内部与继承类的内部可以访问。 -
private
只有类内部可以访问。
3. 继承关系的构造顺序
- 派生类的构造函数与析构函数的调用顺序
- 派生类的构造函数调用顺序
子对象构造、成员变量构造、父对象构造的顺序 - 派生类的析构函数调用顺序
子对象析构、成员变量析构、父对象析构的顺序
#include
using std::cout;
using std::endl;
class Member{
public:
Member(){
cout << "Member Init" <
没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化。
- 同名隐藏规则
概念:子类的成员函数与基类成员函数同名,子类的函数将会隐藏基类的所有同名函数。
#include
using std::cout;
using std::endl;
class Base {
public:
void show(int data){
cout<<"Base::show("<
修改成指针方式,试一下。
int main() {
Derive* pderive = new Derive;
pderive->show();
pderive->show(123);
delete pderive;
}
解决方法:
- 在
Derived
类对象调用被隐藏的父类函数时,在函数前面加上父类限定符。例如:
-
derive.show(123)
→derive.Base::show(123)
-
pderive->show(123)
→pderive->Base::show(123)
-
Derived
类中的名称会隐藏Base
类中同名的名称,在public
继承中我们可以通过引入using
声明。
class Derive:public Base {
public:
using Base::show;
void show(){
cout<<"Derive::show()"<
隐藏背后原因是为防止在程序库或应用框架内建立新的derived class时从疏远的base classes继承重载函数。
--- Effetive C++
函数同名的情况总结
名称 | 英语 |
---|---|
重载 | overload |
重写(覆盖) | override |
隐藏 | hide |
- 赋值兼容规则
概念:在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。
三种情况
- 派生类的对象可以赋值给基类对象。
Base base;
Derive derive;
base = derive;
对象切割(Object Slicing):在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值。
问题:
子类特有的成员变量能否被父类访问?
此时父类对象(
base
)的大小与子类对象(derive
)大小是否一致?
设计一段代码验证派生类的对象可以初始化基类的引用。
Derive derive;
Base& base = derive;
验证上述问题。
- 派生类对象的地址可以赋给指向基类的指针。
指向基类对象的指针变量也可以指向派生类对象。
Derive derive;
Base* base = &derive;
验证上述问题。
- 向上转换:派生类对象赋值给基类
向下转换:基类对象赋值给派生类
派生类的对象可以赋值给基类的对象
int main() {
Base base;
Derive derive;
base = derive;
base.show(123);
//base.show();
//base.Derive::show();
}
- 派生类对象的地址赋给基类的指针变量
int main() {
Base* pbase = new Derive;
pbase->show(123);
//pbase->show();
//pbase->Derive::show();
}
指针访问派生类中由基类继承来的对象,不能访问派生类中的新成员
- 派生类对象可以初始化基类的引用
int main() {
Derive derive;
Base& base = derive;
base.show(123);
//base.show();
//base.Derive::show();
}
引用访问派生类中由基类继承来的对象,不能访问派生类中的新成员
- 多重继承
一个类可以同时继承多个父类的行为和特征功能。
逗号分割的基类列表
class 类名 : public 基类1,public 基类2{
};
案例:等腰直角三角形继承等腰三角形与直角三角形
多重继承基类构造顺序?
钻石继承/菱形继承
概念:两个子类继承同一个父类,而又有子类同时继承这两个子类。
问题:下面
B
、C
继承与A
,D
同时继承B
、C
,那么同时D
的实例调用A
的成员函数会有什么情况?
#include
using std::cout;
using std::endl;
class A{
public:
void test();
private:
int id;
};
void A::test(){
cout << __func__ << endl;
}
class B : public A {};
class C : public A {};
class D : public B,public C{};
int main(){
cout << "A size:" << sizeof(A) << endl;
cout << "B size:" << sizeof(B) << endl;
cout << "C size:" << sizeof(C) << endl;
cout << "D size:" << sizeof(D) << endl;
D d;
d.test();
}
- 原因:
B
与C
都继承了A
的成员函数test()
,D
同时继承了B
与C
,调用test()
无法确定是B
还是C
的。 - 解决:给调用的成员函数前加上访问限定符,明确指定调用成员函数所属的类
d.B::test();
或者d.C::test();
。 - 扩展:尝试
d.A::test()
,会有什么结果?
注意:
D
的sizeof
是B
与C
的和,也就是两个A
。
问题:不同途径继承来的同名的成员在内存中有不同的拷贝,造成数据不一致。
- 解决:虚继承&虚基类
- 虚继承&虚基类
虚继承:在继承定义中包含了virtual
关键字的继承关系。
虚基类:在虚继承体系中的通过virtual
继承而来的基类。
class 类名:public virtual 基类{
}
虚基类是一个相对概念,在虚继承关系中,父类相对与子类是虚基类。
虚基类与普通基类的构造顺序?
扩展:UML
派生类的编码
- 构造与析构
- 继承基类成员
- 重载基类成员函数
- 增加新成员
测验
- 写出下列程序的执行结果,并分析结果。(如果程序编译有错,请分析原因,并写出解决方法)
#include
using namespace std;
class Base{
public:
Base(){
cout << "Base constuct" << endl;
}
~Base(){
cout << "Base destuct" << endl;
}
};
class Member{
public:
Member(){
cout << "Member constuct" << endl;
}
~Member(){
cout << "Member destuct" << endl;
}
};
class Derive:public Base{
public:
Derive(){
cout << "Derive constuct" << endl;
}
~Derive(){
cout << "Derive destuct" << endl;
}
private:
Member m;
};
int main(){
Derive d;
}
关于多重继承
- 什么是多重继承?同时继承多个父类。
- 多重继承有什么危害?菱形继承/钻石继承。
- 什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类。
- 菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类。所以会有完全相同的属性和方法。因此当前多重继承类有两份相同的属性和方法。使用时会出现冲突。
- 如何解决菱形继承/钻石继承导致的冲突?使用虚继承。
- 什么是虚继承?父类在继承具有相同的祖先类时,加上
virtual
.
对象构造顺序总结
- 基本原则
- 先父后子
- 从左到右
- 先虚后实
- 从上到下
- 由内及外
- 示例
#include
using namespace std;
#define SIMPLE_CLASS(name)\
class name{\
public:\
name(){ cout << #name << " Constructor" << endl;}\
~name(){ cout << #name << " Destructor" << endl;}\
};
SIMPLE_CLASS(Base1)
SIMPLE_CLASS(Base2)
SIMPLE_CLASS(Base3)
SIMPLE_CLASS(VBase1)
SIMPLE_CLASS(VBase2)
SIMPLE_CLASS(VBase3)
SIMPLE_CLASS(Member1)
SIMPLE_CLASS(Member2)
SIMPLE_CLASS(Member3)
#undef SIMPLE_CLASS
class Test : public Base1,
public Base2,
public Base3,
public virtual VBase1,
public virtual VBase2,
public virtual VBase3 {
public:
Test() { cout << "Test Constructor" << endl; }
~Test() { cout << "Test Destructor" << endl; }
private:
Member1 m1;
Member2 m2;
Member3 m3;
};
int main() {
Test t;
}
- 执行结果
VBase1 Constructor
VBase2 Constructor
VBase3 Constructor
Base1 Constructor
Base2 Constructor
Base3 Constructor
Member1 Constructor
Member2 Constructor
Member3 Constructor
Test Constructor
Test Destructor
Member3 Destructor
Member2 Destructor
Member1 Destructor
Base3 Destructor
Base2 Destructor
Base1 Destructor
VBase3 Destructor
VBase2 Destructor
VBase1 Destructor