C++的继承
继承的基础语法
C++继承是一个类(派生类)继承了另一个类(基类)的所有成员(成员变量和成员函数),除了构造函数、析构函数以及被声明为私有的成员之外。
继承的定义格式:
继承方式:
【注意点】:
- 同名隐藏:如果派生类定义了一个与基类同名的成员(包括成员变量或成员函数),那么派生类的成员将会隐藏基类的成员。可以使用 作用域解析运算符:: 来明确引用基类的成员。
(尽量避免同名,同名函数不会构成重载,重载要求在相同的作用域中,而基类和派生类都有独立的作用域)
- 友元和静态成员:友元关系不被继承。静态成员属于整个类,而不是某个特定的对象实例,因此无论是基类还是派生类共享相同的静态成员。
基类和派生类对象的赋值转换
- 派生类对象可以赋值给 基类的对象/基类的指针/基类的引用。
(形象说法 "切片",寓意把派生类中父类那部分切来赋值过去)
cpp
//赋值兼容 (切片)
Student s;
Person p = s;
Person* pp = &s;
Person& rp = s;
//不会产生中间临时变量,没有发生类型转换
- 基类对象不能直接赋值给派生类对象。
- 基类的指针或引用可以强制类型转换赋值给派生类的指针或引用。
(必须是基类的指针指向派生类对象才是安全的)

派生类的默认成员函数
-
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
-
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化,派生类的operator= 要调用基类的operator= 完成基类的复制,没有默认的需显式调用。
-
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,以保证派生类对象先清理派生类成员,再清理基类成员的顺序。
(因为析构函数会被统一重写为destructor(),所以基类的析构函数应该声明为virtual)
-
派生类对象初始化先调用基类构造,再调派生类构造。

因为子类可能依赖于父类的成员变量或方法,这些都需要在子类构造之前被正确初始化,所有先构造父类;而析构先释放子类特有的资源,再释放父类资源,是为了避免访问已释放的资源。
菱形继承和菱形虚拟继承

菱形继承:菱形继承是多继承的一种特殊情况。
虚拟继承的写法:
菱形中间有成员被重复继承的两个类,继承方式前加上 virtual 。
这样就解决了数据冗余和访问二义性的问题。为了实现这一点,编译器会在 Student 和 Teacher 类的对象布局中各添加一个指向共享的 Person 实例的指针,这个指针通常被称为虚基表指针(vbptr),它指向同一个虚基表(vbtable),该表记录了如何从各自独有的对象所处于位置到共享对象的偏移量。

IDE里的实例:
虚拟继承总结:知道这个处理办法就行,写代码时尽量避免菱形继承。