子类可以直接获得父类已有的属性和方法,在此基础上扩展新功能,避免重复代码——这就是继承。
假设你要为一个游戏建模,有"战士"和"法师"两种角色。它们都有名字、血量、攻击方法,但战士额外有护甲值,法师额外有魔法值。
如果不用继承,你只能重复写两遍相同的代码——名字、血量、攻击方法在两个类里各写一遍,改一处还要记得改另一处。
继承的解决方案:把公共部分抽出来放在父类 Hero,战士和法师各自继承它,只需要各自写自己独有的部分。
Warrior 和 Mage 都继承自 Hero,自动拥有 name、hp、Attack() 等公共内容,各自只需补充自己独有的部分。
在类名后面加 : public 父类名 就完成了继承。public 是最常用的继承方式,表示父类的 public 成员在子类里仍然是 public 的。
| 1 | // 父类(基类) |
| 2 | class Hero { |
| 3 | public: |
| 4 | string name; |
| 5 | int hp; |
| 6 | void Attack() { cout << name << " 发动攻击!" << endl; } |
| 7 | }; |
| 8 | |
| 9 | // 子类(派生类):继承 Hero |
| 10 | class Warrior : public Hero { // 冒号后写继承方式和父类名 |
| 11 | public: |
| 12 | int armor; // 战士独有的护甲 |
| 13 | void Block() { cout << "举盾格挡!" << endl; } |
| 14 | }; |
| 15 | |
| 16 | class Mage : public Hero { |
| 17 | public: |
| 18 | int mana; // 法师独有的魔法值 |
| 19 | void CastSpell() { cout << "施放魔法!" << endl; } |
| 20 | }; |
| 21 | |
| 22 | int 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.3 节提到了 protected 权限,现在正好用到了。protected 成员对外部代码不可见(和 private 一样),但子类可以直接访问——这正是继承场景的需求。
| 1 | class Hero { |
| 2 | private: |
| 3 | int secretLevel; // 私有:连子类也访问不到 |
| 4 | protected: |
| 5 | int hp; // 保护:子类可以访问,外部不行 |
| 6 | public: |
| 7 | string name; // 公开:所有人都能访问 |
| 8 | }; |
| 9 | |
| 10 | class Warrior : public Hero { |
| 11 | public: |
| 12 | void ShowHp() { |
| 13 | cout << hp; // ✅ protected 成员,子类可以访问 |
| 14 | // cout << secretLevel; ❌ private 成员,子类也不行 |
| 15 | } |
| 16 | }; |
| 17 | |
| 18 | int main() { |
| 19 | Warrior w; |
| 20 | // w.hp = 100; ❌ 外部代码不能访问 protected |
| 21 | w.ShowHp(); // ✅ 通过成员函数间接访问 |
| 22 | } |
protected,把完全不想暴露的内部细节放 private。这是一个常见的设计决策。
创建子类对象时,父类的构造函数会先执行,再执行子类的构造函数。如果父类没有默认构造函数,子类必须在初始化列表里显式调用父类构造函数。
| 1 | class Hero { |
| 2 | public: |
| 3 | string name; int hp; |
| 4 | Hero(string n, int h) : name(n), hp(h) { |
| 5 | cout << "Hero 构造:" << name << endl; |
| 6 | } |
| 7 | }; |
| 8 | |
| 9 | class Warrior : public Hero { |
| 10 | public: |
| 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 | |
| 18 | int main() { |
| 19 | Warrior w("亚瑟", 200, 50); |
| 20 | // 输出顺序: |
| 21 | // Hero 构造:亚瑟 |
| 22 | // Warrior 构造:亚瑟 |
| 23 | } // 析构顺序相反:先 Warrior 析构,再 Hero 析构 |
子类可以重新定义一个与父类同名同参数的函数,叫做覆盖(Override)。调用时,子类对象会执行子类的版本,父类对象执行父类的版本。
| 1 | class Hero { |
| 2 | public: |
| 3 | string name; |
| 4 | void Attack() { cout << name << " 普通攻击" << endl; } |
| 5 | }; |
| 6 | |
| 7 | class Mage : public Hero { |
| 8 | public: |
| 9 | void Attack() { // 覆盖父类的 Attack() |
| 10 | cout << name << " 释放火球术!" << endl; |
| 11 | } |
| 12 | }; |
| 13 | |
| 14 | int 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 关键字。
继承时也有三种方式:public、protected、private。它们决定父类的成员在子类中变成什么权限。竞赛和日常开发中几乎只用 public 继承,另外两种了解即可。
| 父类成员权限 | public 继承后 | protected 继承后 | private 继承后 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
: public 父类名 继承,自动获得父类的 public 和 protected 成员。protected:子类可访问,外部不可访问,专为继承设计。virtual(下一节)。