C++笔记(面向对象)多重继承 菱形继承
C++中的多重继承(Multiple Inheritance)。这是一个强大但需要谨慎使用的特性。
1. 多重继承的基本语法
cpp
class Base1 {
public:void func1() { cout << "Base1::func1\n"; }
};class Base2 {
public:void func2() { cout << "Base2::func2\n"; }
};// 多重继承:Derived同时继承Base1和Base2
class Derived : public Base1, public Base2 {
public:void derivedFunc() { func1(); // 调用Base1的方法func2(); // 调用Base2的方法}
};int main() {Derived d;d.func1(); // ✅ 来自Base1d.func2(); // ✅ 来自Base2d.derivedFunc();// ✅ 自己的方法
}2. 多重继承的构造和析构顺序
构造顺序:
按继承声明顺序构造基类
按声明顺序构造成员对象
最后构造派生类自己
析构顺序:完全相反
cpp
#include <iostream>
using namespace std;class Base1 {
public:Base1() { cout << "Base1构造\n"; }~Base1() { cout << "Base1析构\n"; }
};class Base2 {
public:Base2() { cout << "Base2构造\n"; }~Base2() { cout << "Base2析构\n"; }
};class Member {
public:Member() { cout << "Member构造\n"; }~Member() { cout << "Member析构\n"; }
};// 继承声明顺序:Base1 → Base2
class Derived : public Base1, public Base2 {Member mem; // 成员对象
public:Derived() { cout << "Derived构造\n"; }~Derived() { cout << "Derived析构\n"; }
};int main() {Derived d;return 0;
}输出结果:
text
Base1构造 Base2构造 Member构造 Derived构造 Derived析构 Member析构 Base2析构 Base1析构
3. 名字冲突与二义性
当多个基类有同名成员时,会产生二义性:
cpp
class Base1 {
public:void show() { cout << "Base1::show\n"; }int value = 100;
};class Base2 {
public:void show() { cout << "Base2::show\n"; }int value = 200;
};class Derived : public Base1, public Base2 {
public:void test() {// show(); // ❌ 错误:二义性,不知道调用哪个show// value = 5; // ❌ 错误:二义性,不知道访问哪个valueBase1::show(); // ✅ 明确指定Base1的showBase2::show(); // ✅ 明确指定Base2的showBase1::value = 10; // ✅ 明确指定Base1的valueBase2::value = 20; // ✅ 明确指定Base2的value}
};4. 使用using声明解决名字冲突
可以在派生类中使用using声明引入特定基类的成员:
cpp
class Derived : public Base1, public Base2 {
public:using Base1::show; // 引入Base1的showusing Base1::value; // 引入Base1的valuevoid test() {show(); // ✅ 现在使用Base1的show(因为using声明)value = 50; // ✅ 使用Base1的valueBase2::show(); // ✅ 仍然可以明确调用Base2的showBase2::value = 60; // ✅ 仍然可以访问Base2的value}
};菱形继承(Diamond Inheritance)是C++多重继承中一个经典且重要的问题。我们来详细解析它。
1. 什么是菱形继承?
菱形继承指的是一个类从两个父类继承,而这两个父类又共同继承自同一个基类。
cpp
class Animal {
public:int age;
};class Mammal : public Animal {// 包含Animal的成员
};class Bird : public Animal {// 包含Animal的成员
};class Bat : public Mammal, public Bird { // 菱形继承// 问题:包含两个Animal子对象!
};内存布局示意图:
text
Animal/ \ Mammal Bird\ /BatBat对象在内存中: +-------------+ | Mammal部分 | | Animal | <- 第一个Animal子对象 | age | +-------------+ | Bird部分 | | Animal | <- 第二个Animal子对象 | age | +-------------+ | Bat自有成员 | +-------------+
2. 菱形继承的问题
数据冗余和二义性
cpp
Bat bat; // bat.age = 5; // ❌ 错误:二义性,不知道访问哪个age bat.Mammal::age = 3; // ✅ 明确指定 bat.Bird::age = 4; // ✅ 明确指定cout << bat.Mammal::age << endl; // 输出3 cout << bat.Bird::age << endl; // 输出4 // 两个不同的age!数据冗余
函数调用的二义性
cpp
class Animal {
public:void breathe() { cout << "Animal breathing\n"; }
};class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};Bat bat;
// bat.breathe(); // ❌ 错误:二义性
bat.Mammal::breathe(); // ✅
bat.Bird::breathe(); // ✅3. 解决方案:虚继承(Virtual Inheritance)
虚继承可以解决菱形继承中的数据冗余和二义性问题。
cpp
class Animal {
public:int age;void breathe() { cout << "Animal breathing\n"; }
};// 使用虚继承
class Mammal : virtual public Animal { // 虚继承
};class Bird : virtual public Animal { // 虚继承
};class Bat : public Mammal, public Bird { // 现在只有一个Animal子对象
};虚继承后的内存布局:
text
Animal (虚基类)/ \ Mammal Bird\ /BatBat对象在内存中: +-------------+ | Mammal部分 | | (虚基类指针)| +-------------+ | Bird部分 | | (虚基类指针)| +-------------+ | Bat自有成员 | +-------------+ | Animal部分 | <- 唯一的Animal子对象 | age | +-------------+
4. 虚继承的效果
cpp
Bat bat; bat.age = 5; // ✅ 不再有二义性! bat.breathe(); // ✅ 可以直接调用bat.Mammal::age = 5; // ✅ 仍然可以,但访问的是同一个age bat.Bird::age = 5; // ✅ 同一个agecout << bat.Mammal::age << endl; // 输出5 cout << bat.Bird::age << endl; // 输出5(同一个值)
5. 虚继承的构造顺序
虚继承的构造顺序比较特殊:
cpp
#include <iostream>
using namespace std;class Animal {
public:Animal() { cout << "Animal构造\n"; }
};class Mammal : virtual public Animal {
public:Mammal() { cout << "Mammal构造\n"; }
};class Bird : virtual public Animal {
public:Bird() { cout << "Bird构造\n"; }
};class Bat : public Mammal, public Bird {
public:Bat() { cout << "Bat构造\n"; }
};int main() {Bat bat;return 0;
}输出结果:
text
Animal构造 ← 虚基类最先构造 Mammal构造 Bird构造 Bat构造
虚继承构造规则:
虚基类最先构造(即使不在初始化列表中)
然后按继承声明顺序构造非虚基类
最后构造派生类自己
6. 虚继承的析构顺序
与构造顺序完全相反:
cpp
~Bat() { cout << "Bat析构\n"; }
~Bird() { cout << "Bird析构\n"; }
~Mammal() { cout << "Mammal析构\n"; }
~Animal() { cout << "Animal析构\n"; }输出:
text
Bat析构 Bird析构 Mammal析构 Animal析构
7. 带参数的虚继承构造函数
虚基类的构造由最底层的派生类负责:
cpp
class Animal {int weight;
public:Animal(int w) : weight(w) { cout << "Animal构造\n"; }
};class Mammal : virtual public Animal {
public:Mammal(int w) : Animal(w) { cout << "Mammal构造\n"; }
};class Bird : virtual public Animal {
public:Bird(int w) : Animal(w) { cout << "Bird构造\n"; }
};class Bat : public Mammal, public Bird {
public:// 必须负责虚基类Animal的构造Bat(int w) : Animal(w), Mammal(w), Bird(w) { // Animal必须在初始化列表中cout << "Bat构造\n";}
};重要: 即使Mammal和Bird的初始化列表中有Animal(w),实际执行时只有Bat中的Animal(w)会起作用。
8. 实际应用场景
典型的菱形继承案例:
cpp
// 流类层次
class ios_base { /* 基础功能 */ };class istream : virtual public ios_base { // 输入功能
};class ostream : virtual public ios_base {// 输出功能
};class iostream : public istream, public ostream {// 输入输出功能
};人员管理系统:
cpp
class Person {
public:string name;int age;
};class Student : virtual public Person {
public:string studentId;
};class Employee : virtual public Person {
public:string employeeId;
};class TeachingAssistant : public Student, public Employee {// 只有一份Person数据,不会重复
public:TeachingAssistant(const string& n, int a) {name = n; // ✅ 明确的age = a; // ✅ 明确的}
};9. 虚继承的代价
虚继承不是免费的午餐:
性能开销:通过虚基类指针间接访问
内存开销:每个虚继承的类需要存储虚基类指针
构造复杂:最底层派生类负责虚基类构造
理解成本:代码更复杂,维护难度增加
10. 最佳实践建议
谨慎使用多重继承,优先考虑组合
如果必须菱形继承,使用虚继承解决二义性
保持继承层次简单,避免过深的菱形继承
明确使用场景:
✅ 接口多重继承(Java风格)
✅ 混入类(Mixin)
❌ 过度复杂的类层次
cpp
// 好的使用:接口继承
class Drawable { // 接口
public:virtual void draw() = 0;
};class Clickable { // 接口
public:virtual void click() = 0;
};class Button : public Drawable, public Clickable { // 多重接口继承
public:void draw() override { /* 实现 */ }void click() override { /* 实现 */ }
};总结: 菱形继承通过虚继承解决,但要理解其代价,在确实需要时才使用。
