多重继承
多重继承基本概念
定义
多重继承是C++独有的面向对象特性(Java、C#等语言不原生支持),指一个类同时继承自多个父类,子类会拥有所有父类的属性和方法,对应现实中“一个事物具有多个身份”的场景。
语法格式:
class 子类名 : 继承方式 父类1, 继承方式 父类2, ..., 继承方式 父类n {
// 子类成员
};
示例:水陆两栖装甲车(既是车也是船)
// 父类1:车
class Car {
int x;
public:
Car(int x=0):x(x){}
void show(){cout << x << endl;}
};
// 父类2:船
class Boat {
float y;
public:
Boat(float y=0.0):y(y){}
void show(){cout << y << endl;}
};
// 子类:水陆两栖装甲车(多重继承Car和Boat)
class AmphibiousVehicle : public Car, public Boat {
public:
AmphibiousVehicle(int x=0, float y=0.0);
};
构造函数
多重继承的子类构造时,需在初始化列表中显式调用每个父类的构造函数(若未显式调用,则默认调用父类的无参构造函数)。
实现示例:
// 子类构造函数实现
AmphibiousVehicle::AmphibiousVehicle(int x, float y)
: Car(x), Boat(y) // 按继承顺序调用父类构造
{
// 子类自身初始化逻辑
}
二义性问题
当多个父类拥有重名的方法或属性时,子类直接调用该成员会产生“二义性”(编译器无法确定调用哪个父类的版本),导致编译错误。
错误示例:
int main() {
AmphibiousVehicle v(100, 1.23);
v.show(); // 错误:Car和Boat都有show(),二义性
}
重名成员处理
解决二义性的核心是使用 域解析符:: 明确指定父类来源。
正确调用示例:
int main() {
AmphibiousVehicle v(100, 1.23);
v.Car::show(); // 调用Car类的show(),输出100
v.Boat::show(); // 调用Boat类的show(),输出1.23
// 若有重名属性,同样用域解析符
// 假设Car和Boat都有name属性
// v.Car::name = "越野车";
// v.Boat::name = "快艇";
}
拓展:多重继承的优缺点
优点
- 直观表达“多身份”场景(如两栖车=车+船)
- 代码复用更灵活,可整合多个父类的功能
缺点
- 二义性问题:重名成员需手动处理
- 逻辑复杂度高:类关系混乱,可读性下降
- 菱形继承陷阱:多个父类继承自同一基类时产生数据冗余
- 兼容性差:其他OOP语言(Java、C#)不支持,跨语言移植困难
菱形继承(钻石继承)
定义与问题
当多重继承的多个父类共同继承自同一个基类时,子类会包含该基类的多个副本(数据冗余),类关系呈菱形结构,称为菱形继承。
核心问题
- 数据冗余:子类拥有基类的多份成员(如示例中AmphibiousVehicle有2个Vehicle的trade成员)
- 逻辑矛盾:同一属性应唯一(如交通工具的生产厂商不应有2个)
典型场景分析
以交通类为例:
// 基类:交通工具
class Vehicle {
public:
string trade; // 生产厂商(应唯一)
};
// 父类1:汽车(继承Vehicle)
class Car : public Vehicle {
// 汽车特有成员
};
// 父类2:船(继承Vehicle)
class Boat : public Vehicle {
// 船特有成员
};
// 子类:水陆两栖装甲车(继承Car和Boat)
class AmphibiousVehicle : public Car, public Boat {
// 两栖车特有成员
};
问题演示
int main() {
AmphibiousVehicle v;
// 二义性:两个trade成员,需指定来源
v.Car::trade = "中国兵器工业集团";
v.Boat::trade = "中国船舶工业集团"; // 逻辑矛盾:一个交通工具不应有两个厂商
}
拓展:菱形继承的其他表现形式
菱形继承并非严格的“四层结构”,以下场景也属于菱形继承变体:
- 多层传递:基类A → B → D,A → C → D(仍是菱形结构)
- 多中间类:A → B、A → C、A → E → D(D继承B、C、E,仍含多份A)
虚继承(解决菱形继承)
语法规则
C++引入虚继承机制,通过virtual关键字修饰继承关系,使末端子类仅保留基类的一份副本,解决数据冗余和逻辑矛盾。
语法格式:
// 中间类(如Car、Boat)虚继承基类(如Vehicle)
class 中间类名 : virtual 继承方式 基类名 {
// 类成员
};
示例:
// 基类:交通工具
class Vehicle {
public:
string trade;
Vehicle(string t="null"):trade(t){}
void company()const{cout << "生产厂商:" << trade << endl;}
};
// 中间类1:汽车(虚继承Vehicle)
class Car : virtual public Vehicle {
int x;
public:
Car(int x=0, string t="null"):Vehicle(t), x(x){}
void show(){cout << x << endl;}
};
// 中间类2:船(虚继承Vehicle)
class Boat : virtual public Vehicle { // virtual位置可在public前后
float y;
public:
Boat(float y=0.0, string t="null"):Vehicle(t), y(y){}
void show(){cout << y << endl;}
};
核心原理
虚继承的核心是改变基类成员的归属权:
- 非虚继承:中间类(B、C)各自拥有基类(A)的副本,末端类(D)继承中间类的副本
- 虚继承:基类(A)的副本直接归属于末端类(D),中间类(B、C)仅保留“引用”,不再拥有独立副本
代码实现
末端子类需显式调用基类的构造函数(虚继承后,中间类不再自动调用基类构造):
// 末端子类:水陆两栖装甲车
class AmphibiousVehicle : public Car, public Boat {
public:
// 必须显式调用虚基类Vehicle的构造函数(否则调用默认构造)
AmphibiousVehicle(string t="null", int x=0, float y=0.0)
: Vehicle(t), // 直接初始化虚基类
Car(x), // 中间类构造,无需再传t(虚基类由末端子类初始化)
Boat(y) {}
};
// 测试代码
int main(int argc, char const *argv[]) {
AmphibiousVehicle v("中国兵器工业集团有限公司", 100, 1.23);
v.company(); // 输出:生产厂商:中国兵器工业集团有限公司(仅一份副本)
return 0;
}
注意事项
virtual关键字的作用范围:仅对“末端子类”有效,中间类(如Car、Boat)自身构造时仍会正常调用基类构造。- 构造函数调用顺序:虚基类的构造函数优先于非虚基类和中间类执行。
virtual与多态的区别:此处virtual是“虚继承”关键字,与“虚函数”(多态)无任何关系,仅复用关键字。- 多重虚继承:若有多个虚基类,末端子类需在初始化列表中依次调用所有虚基类的构造函数。
拓展:虚继承的底层实现(虚基类表)
C++编译器通过虚基类表(Virtual Base Table, VBT) 实现虚继承:
- 中间类(如Car、Boat)会增加一个“虚基类表指针(vbptr)”,指向虚基类表
- 虚基类表中存储中间类到虚基类(Vehicle)的偏移量
- 末端子类(AmphibiousVehicle)初始化时,通过偏移量找到唯一的虚基类实例,避免多份副本
底层结构简化示意图:
AmphibiousVehicle 对象:
├─ vbptr(Car的虚基类表指针)
├─ x(Car的成员)
├─ vbptr(Boat的虚基类表指针)
├─ y(Boat的成员)
└─ Vehicle 实例(唯一副本)
└─ trade
核心知识点总结
| 概念 | 核心要点 |
|---|---|
| 多重继承 | 一个类继承多个父类,需处理重名成员的二义性(域解析符::) |
| 菱形继承 | 多个父类继承自同一基类,导致基类成员多份副本,逻辑矛盾 |
| 虚继承 | 用virtual修饰中间类的继承关系,使末端子类仅保留基类一份副本 |
| 构造函数 | 虚继承时,末端子类需显式调用虚基类构造函数,优先级高于中间类 |
| 二义性解决 | 重名成员用子类对象.父类名::成员指定来源 |
关键结论
- 多重继承慎用:仅在确有“多身份”场景时使用,避免类关系复杂
- 菱形继承必用虚继承:否则会产生数据冗余和逻辑错误
- 虚继承仅修饰中间类:末端子类无需加
virtual,仅中间类(如Car、Boat)需虚继承基类
练习:交通类设计实战
要求
编程实现以下类,运用多重继承和虚继承技巧:
| 类名 | 关系 | 要求 |
|---|---|---|
| Vehicle(交通工具) | 基类 | 1. 虚函数move()(行走功能);2. 实函数getID()(获取牌照);3. 牌照属性ID |
| Car(汽车) | 虚继承Vehicle | 汽车特有成员(如wheelNum车轮数) |
| Boat(船) | 虚继承Vehicle | 船特有成员(如displacement排水量) |
| AmphibiousVehicle | 多重继承Car和Boat | 两栖车特有成员(如armorThickness装甲厚度) |
参考实现
#include <iostream>
#include <string>
using namespace std;
// 基类:交通工具
class Vehicle {
protected:
string ID; // 牌照
public:
Vehicle(string id=""):ID(id){}
// 虚函数:行走功能(多态)
virtual void move() {
cout << "交通工具移动" << endl;
}
// 实函数:获取牌照
string getID() {
return ID;
}
};
// 汽车类:虚继承Vehicle
class Car : virtual public Vehicle {
protected:
int wheelNum; // 车轮数
public:
Car(string id="", int num=4):Vehicle(id), wheelNum(num){}
void move() override {
cout << "汽车用" << wheelNum << "个轮子行驶" << endl;
}
};
// 船类:虚继承Vehicle
class Boat : virtual public Vehicle {
protected:
float displacement; // 排水量(单位:吨)
public:
Boat(string id="", float dis=100.0):Vehicle(id), displacement(dis){}
void move() override {
cout << "船用排水量" << displacement << "吨的船体航行" << endl;
}
};
// 水陆两栖装甲车:多重继承Car和Boat
class AmphibiousVehicle : public Car, public Boat {
private:
float armorThickness; // 装甲厚度(单位:mm)
public:
// 显式调用虚基类Vehicle的构造函数
AmphibiousVehicle(string id="", int num=4, float dis=200.0, float thick=10.0)
: Vehicle(id), Car(id, num), Boat(id, dis), armorThickness(thick){}
// 重写move(),实现两栖功能
void move() override {
cout << "两栖装甲车:";
Car::move(); // 调用汽车的move()
Boat::move(); // 调用船的move()
cout << "装甲厚度:" << armorThickness << "mm" << endl;
}
};
// 测试
int main() {
AmphibiousVehicle av("军A12345", 6, 300.0, 15.0);
cout << "牌照:" << av.getID() << endl; // 唯一牌照,无歧义
av.move(); // 多态调用,输出两栖功能
return 0;
}
拓展:相关特性与对比
与Java的对比(不支持多重继承的替代方案)
Java不支持类的多重继承,但提供两种替代方案:
- 接口(Interface):一个类可实现多个接口,接口仅含抽象方法和常量,无数据冗余
- 单继承+组合:通过“has-a”关系组合多个类的功能,而非“is-a”的继承关系
示例(Java接口替代):
// 接口:可行驶
interface Drivable { void drive(); }
// 接口:可航行
interface Navigable { void navigate(); }
// 类:两栖车实现两个接口
class AmphibiousVehicle implements Drivable, Navigable {
public void drive() { /* 行驶逻辑 */ }
public void navigate() { /* 航行逻辑 */ }
}
C++11后的多重继承增强
override关键字:明确标记重写父类虚函数,避免意外隐藏(如上述练习中的move() override)final关键字:禁止类被继承或函数被重写,限制多重继承的复杂度class FinalClass final { /* 此类不能被继承 */ }; class Parent { virtual void func() final; /* 此函数不能被重写 */ };
多重继承的实际应用场景
- 混合类(Mixin):提供通用功能的类(如日志类、序列化类),通过多重继承混入主类
class Loggable { // 日志混合类 public: void log(string msg) { cout << "日志:" << msg << endl; } }; class Data : public Loggable, public Serializable { // 混入日志和序列化功能 // ... }; - 多角色类:如“学生运动员”(继承Student类和Athlete类)、“管理员用户”(继承User类和Admin类)
常见陷阱与避坑指南
- 避免深层多重继承:继承层次超过3层+多重继承,会导致代码可读性极差
- 优先组合而非继承:若仅需复用功能,而非“is-a”关系,用组合(如
class A { B b; };)替代继承 - 虚基类尽量简单:虚基类应仅包含公共属性和方法,避免复杂构造逻辑
- 明确重写虚函数:多重继承中虚函数易被隐藏,需用
override明确标记

浙公网安备 33010602011771号