概念
继承是从已有的类创建新类的过程,这使得创建和维护一个应用程序变得更容易,达到了重用代码功能和提高执行时间的效率
继承呈现了面向对象程序设计的层次结构,体现了从简单到复杂的认知过程
在 c++ 类中,如类 B 继承于 类 A,则类 A 叫做 基类或者父类,则类 B 叫做 派生类或者子类
语法
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,...
{
<派生类类体>
}
继承方式
公有继承(public继承方式)
-
基类中所有 public 成员在派生类中为 public 属性;
-
基类中所有 protected 成员在派生类中为 protected 属性;
-
基类中所有 private 成员在派生类中不能使用。
基类中的私有成员,就算被public继承,可不可以直接通过派生类的公有成员函数访问,必须通过基类中的公有或者保护函数接口访问
#include <iostream> using namespace std; class A { public: //基类和派生类中出现同名后,在各自类中的构造函数中初始化 A() : x(100), a(200) { } //基类私有成员通过公有接口可以访问 int getInfo() { cout << a << endl; } int x; private: int a; }; class B : public A { public: B() : x(0) {} //公共接口中,可以调用基类的公有函数接口 void getAInfo() { //区分基类和派生类的同名成员变量,可以使用作用域 cout << x << endl; cout << A::x << endl; getInfo(); } double d; char c; int x; }; int main(int argc, const char *argv[]) { cout << sizeof(A) << endl; cout << sizeof(B) << endl; A a; B b; //区分基类和派生类的同名成员变量,可以使用作用域 cout << b.x << endl; cout << b.d << endl; cout << b.A::x << endl; b.getAInfo(); return 0; }
保护继承(protected继承方式)
- 基类中的所有 public 成员在派生类中为 protected 属性;
- 基类中的所有 protected 成员在派生类中为 protected 属性;
- 基类中的所有 private 成员在派生类中不能使用。
私有继承(private继承方式)
- 基类中的所有 public 成员在派生类中均为 private 属性;
- 基类中的所有 protected 成员在派生类中均为 private 属性;
- 基类中的所有 private 成员在派生类中不能使用。
继承方式/基类成员 | public成员 | protected成员 | private成员 |
---|---|---|---|
public继承 | public | protected | 不可见 |
protected继承 | protected | protected | 不可见 |
private继承 | private | private | 不可见 |
基类的指针或者引用可以指向派生类,反之不可以
组合
class A
{
public:
void show()
{
cout << "A" << endl;
}
};
class B
{
public:
void show()
{
cout << "B" << endl;
}
};
class C : public A
{
public:
void show()
{
cout << "C" << endl;
A::show(); //继承
b.show(); //组合
}
private:
B b;
};
继承中构造函数的调用顺序
- 先调用父类构造函数 – 调用构造函数的顺序跟声明顺序有关
- 再调用成员对象构造函数(只跟声明顺序有关)
- 最后调用自身构造函数
继承中析构函数的调用顺序
和构造函数相反
多继承与多重继承
多继承:一个派生类有多个基类
多重继承:一个派生类作为其他派生类的基类
问题:
当D类继承于B类和C类,B类与C类有一个共同的基类时,在创建D类的对象时,A类的构造函数将会调用两次,相当于创建两个A类对象
解决方案:
-
避免使用这种方式
-
使用虚继承
class A { public: A() { cout << "A structor" << endl; } ~A() { cout << "A destructor" << endl; } }; class B : virtual public A { public: B() { cout << "B structor" << endl; } ~B() { cout << "B destructor" << endl; } int a; }; class C : virtual public A { public: C() { cout << "C structor" << endl; } ~C() { cout << "C destructor" << endl; } int a; }; class E : virtual public C, virtual public B { }
使用虚继承后,在虚继承的类中,会有一个虚基表指针vbptr,指向虚基表,虚基表中会存在偏移量,这个量就是表的地址到父类数据地址的距离
注意点:
- 虚基类构造函数调用顺序:基类 --> 中间派生类 --> 最末派生类
- 虚基类析构函数调用顺序:最末派生类 --> 中间派生类 --> 基类
- 虚基类的初始化:基类对象在最末派生类中初始化
- VTT(Virtual Table Table):在多重虚继承中,如果一个派生类从多个路径继承同一个虚基类,那么就可能需要处理不同的虚函数表之间的交叉问题。VTT 表的作用就是为了记录虚函数表的地址,以便在处理多重虚继承时能够正确地选择调用哪个虚函数