← 目录 / 第十二章 · 类与面向对象 / 12.8 继承基础

12.8 继承基础

子类可以直接获得父类已有的属性和方法,在此基础上扩展新功能,避免重复代码——这就是继承。

本页目录
12.8.1 为什么需要继承

假设你要为一个游戏建模,有"战士"和"法师"两种角色。它们都有名字、血量、攻击方法,但战士额外有护甲值,法师额外有魔法值。

如果不用继承,你只能重复写两遍相同的代码——名字、血量、攻击方法在两个类里各写一遍,改一处还要记得改另一处。

继承的解决方案:把公共部分抽出来放在父类 Hero,战士和法师各自继承它,只需要各自写自己独有的部分。

Hero(父类) name, hp Attack(), ShowInfo() 公共属性和方法 Warrior(战士) armor(护甲) + 继承 Hero 的全部 Mage(法师) mana(魔法值) + 继承 Hero 的全部 继承关系(箭头指向父类)
WarriorMage 都继承自 Hero,自动拥有 namehpAttack() 等公共内容,各自只需补充自己独有的部分。
12.8.2 继承的基本语法

在类名后面加 : public 父类名 就完成了继承。public 是最常用的继承方式,表示父类的 public 成员在子类里仍然是 public 的。

C++ · 继承的基本写法
1// 父类(基类)
2class Hero {
3public:
4 string name;
5 int hp;
6 void Attack() { cout << name << " 发动攻击!" << endl; }
7};
8
9// 子类(派生类):继承 Hero
10class Warrior : public Hero { // 冒号后写继承方式和父类名
11public:
12 int armor; // 战士独有的护甲
13 void Block() { cout << "举盾格挡!" << endl; }
14};
15
16class Mage : public Hero {
17public:
18 int mana; // 法师独有的魔法值
19 void CastSpell() { cout << "施放魔法!" << endl; }
20};
21
22int main() {
23 Warrior w;
24 w.name = "亚瑟"; // ✅ 继承自 Hero 的成员,直接用
25 w.hp = 200;
26 w.armor = 50; // ✅ Warrior 自己的成员
27 w.Attack(); // ✅ 继承自 Hero 的方法
28 w.Block(); // ✅ Warrior 自己的方法
29}
12.8.3 protected:专为继承设计的权限

12.3 节提到了 protected 权限,现在正好用到了。protected 成员对外部代码不可见(和 private 一样),但子类可以直接访问——这正是继承场景的需求。

C++ · private vs protected 在继承中的区别
1class Hero {
2private:
3 int secretLevel; // 私有:连子类也访问不到
4protected:
5 int hp; // 保护:子类可以访问,外部不行
6public:
7 string name; // 公开:所有人都能访问
8};
9
10class Warrior : public Hero {
11public:
12 void ShowHp() {
13 cout << hp; // ✅ protected 成员,子类可以访问
14 // cout << secretLevel; ❌ private 成员,子类也不行
15 }
16};
17
18int main() {
19 Warrior w;
20 // w.hp = 100; ❌ 外部代码不能访问 protected
21 w.ShowHp(); // ✅ 通过成员函数间接访问
22}
📌
设计父类时,把子类需要直接操作的数据protected,把完全不想暴露的内部细节private。这是一个常见的设计决策。
12.8.4 构造函数的调用链

创建子类对象时,父类的构造函数会先执行,再执行子类的构造函数。如果父类没有默认构造函数,子类必须在初始化列表里显式调用父类构造函数。

C++ · 构造函数调用链
1class Hero {
2public:
3 string name; int hp;
4 Hero(string n, int h) : name(n), hp(h) {
5 cout << "Hero 构造:" << name << endl;
6 }
7};
8
9class Warrior : public Hero {
10public:
11 int armor;
12 Warrior(string n, int h, int a)
13 : Hero(n, h), armor(a) { // 先调用父类构造函数
14 cout << "Warrior 构造:" << name << endl;
15 }
16};
17
18int main() {
19 Warrior w("亚瑟", 200, 50);
20 // 输出顺序:
21 // Hero 构造:亚瑟
22 // Warrior 构造:亚瑟
23} // 析构顺序相反:先 Warrior 析构,再 Hero 析构
💡
构造顺序:父类先,子类后——就像盖房子先打地基再建上层。
析构顺序:子类先,父类后——拆房子先拆上层再拆地基。
12.8.5 方法覆盖(Override)

子类可以重新定义一个与父类同名同参数的函数,叫做覆盖(Override)。调用时,子类对象会执行子类的版本,父类对象执行父类的版本。

C++ · 子类覆盖父类方法
1class Hero {
2public:
3 string name;
4 void Attack() { cout << name << " 普通攻击" << endl; }
5};
6
7class Mage : public Hero {
8public:
9 void Attack() { // 覆盖父类的 Attack()
10 cout << name << " 释放火球术!" << endl;
11 }
12};
13
14int main() {
15 Hero h; h.name = "骑士";
16 Mage m; m.name = "莫甘娜";
17 h.Attack(); // 输出:骑士 普通攻击
18 m.Attack(); // 输出:莫甘娜 释放火球术!(子类版本)
19
20 // 如果想在子类里调用父类的版本,用 父类名:: 显式指定
21 Hero::Attack(); // 在 Mage 的成员函数内调用父类版本
22}
⚠️
这里的覆盖是静态的——编译器在编译时就根据变量的类型决定调用哪个版本。如果用父类指针指向子类对象,调用的仍然是父类版本。要实现"运行时动态决定",需要下一节的 virtual 关键字。
12.8.6 继承与访问权限

继承时也有三种方式:publicprotectedprivate。它们决定父类的成员在子类中变成什么权限。竞赛和日常开发中几乎只用 public 继承,另外两种了解即可。

父类成员权限public 继承后protected 继承后private 继承后
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问
📖
本节小结
・子类用 : public 父类名 继承,自动获得父类的 public 和 protected 成员。
protected:子类可访问,外部不可访问,专为继承设计。
・创建子类对象时,父类构造函数先执行;析构时,子类析构函数先执行
・子类可以覆盖父类的同名方法,但这是静态绑定——动态绑定需要 virtual(下一节)。