📌 专栏说明:本专栏「从零开始学C++」面向完全零基础的同学,每篇文章聚焦一个核心概念,用最通俗的语言 + 完整可运行的代码帮你把知识点吃透。本文主题:继承(Inheritance)

C++相关专题链接🔗:

https://blog.csdn.net/xiao_running/category_13158015.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=13158015&sharerefer=PC&sharesource=Xiao_running&sharefrom=from_link


🧭 前言

在学会了类与对象之后,相信你已经能写出一些简单的程序了。但随着代码量的增加,你会发现:不同的类之间,往往有很多相似甚至相同的成员变量和成员函数

比如你写了一个"动物"类,里面有名字、年龄、叫声等属性;之后你还要写"狗"类、"猫"类……难道每次都要把这些公共属性重新写一遍吗?

当然不用!继承 就是 C++ 给出的解决方案,让你可以复用已有的代码,在此基础上扩展新功能


一、继承是什么?

简介与作用

继承(Inheritance) 是面向对象编程(OOP)三大特性之一。

  • 父类(基类):被继承的类,提供公共的属性和行为。
  • 子类(派生类):继承父类,在拥有父类所有"允许访问"成员的同时,还可以添加自己独有的成员。

继承的核心价值:

  1. 代码复用:公共代码写一次,多处使用。
  2. 扩展性强:子类可以在父类基础上添加新功能,不影响父类。
  3. 逻辑清晰:用"is-a"关系建模,狗 is-a 动物,学生 is-a 人……

💡 通俗来说:继承就像"继承家业",父亲留下的东西(属性+方法),儿子可以直接用,还能自己再添置新家产。


代码示例

示例一:最基础的继承写法
#include <iostream>
#include <string>
using namespace std;

// 父类(基类)
class Animal {
public:
    string name;
    int age;

    void eat() {
        cout << name << " 正在吃东西" << endl;
    }

    void sleep() {
        cout << name << " 正在睡觉" << endl;
    }
};

// 子类(派生类),Dog 继承自 Animal
class Dog : public Animal {
public:
    // Dog 独有的方法
    void bark() {
        cout << name << " 汪汪汪!" << endl;
    }
};

int main() {
    Dog dog;
    dog.name = "小黑";  // 继承自 Animal
    dog.age = 3;        // 继承自 Animal
    dog.eat();          // 继承自 Animal
    dog.bark();         // Dog 自己的方法
    return 0;
}

输出结果:

小黑 正在吃东西
小黑 汪汪汪!

✅ 可以看到,Dog 类没有定义 nameageeat(),却可以直接使用,这就是继承的魔力。


二、继承的三种访问方式

简介与作用

继承时,冒号后面可以加 publicprotectedprivate 三个关键字,它们决定了父类成员在子类中的可见范围

父类成员权限 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

📌 日常使用最多的是 public 继承protected 和 private 继承较少见,初学阶段掌握 public 继承即可。

记住一个关键规则父类的 private 成员,子类永远无法直接访问(无论哪种继承方式)。

实际上⾯的表格我们进⾏⼀下总结会发现,基类的私有成员在派⽣类都是不可⻅。基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),public >protected> private。


代码示例

示例二:三种继承方式的访问权限对比
#include <iostream>
using namespace std;

class Base {
public:
    int pub = 1;
protected:
    int pro = 2;
private:
    int pri = 3;  // 任何继承方式下,子类都无法直接访问
};

// public 继承:pub->public, pro->protected
class PublicDerived : public Base {
public:
    void show() {
        cout << "pub = " << pub << endl;  // ✅ 可访问
        cout << "pro = " << pro << endl;  // ✅ 可访问
        // cout << pri << endl;           // ❌ 编译错误!private 不可访问
    }
};

// protected 继承:pub->protected, pro->protected
class ProtectedDerived : protected Base {
public:
    void show() {
        cout << "pub = " << pub << endl;  // ✅ 可访问(降为protected)
        cout << "pro = " << pro << endl;  // ✅ 可访问
    }
};

int main() {
    PublicDerived pd;
    pd.show();
    cout << "外部访问 pd.pub = " << pd.pub << endl;  // ✅ public 继承,pub 还是 public

    ProtectedDerived prd;
    prd.show();
    // cout << prd.pub << endl;  // ❌ protected 继承后,pub 变成 protected,外部不可访问
    return 0;
}

三、继承中的构造与析构顺序

简介与作用

当我们创建一个子类对象时,父类的构造函数和子类的构造函数都会被调用。同理,对象销毁时,析构函数也都会被调用。

调用顺序规则:

  • 构造:先执行父类构造函数,再执行子类构造函数(从上到下)
  • 析构:先执行子类析构函数,再执行父类析构函数(从下到上,与构造相反)

💡 这很好理解:儿子出生之前,父亲必须先存在;儿子去世之后,父亲才离开。


代码示例

示例三:观察构造与析构的调用顺序cpp
#include <iostream>
using namespace std;

class Father {
public:
    Father() {
        cout << "Father 构造函数被调用" << endl;
    }
    ~Father() {
        cout << "Father 析构函数被调用" << endl;
    }
};

class Son : public Father {
public:
    Son() {
        cout << "Son 构造函数被调用" << endl;
    }
    ~Son() {
        cout << "Son 析构函数被调用" << endl;
    }
};

int main() {
    cout << "--- 创建 Son 对象 ---" << endl;
    Son s;
    cout << "--- 对象即将销毁 ---" << endl;
    return 0;  // s 离开作用域,析构函数自动调用
}

输出结果:

--- 创建 Son 对象 ---
Father 构造函数被调用
Son 构造函数被调用
--- 对象即将销毁 ---
Son 析构函数被调用
Father 析构函数被调用

示例四:子类构造函数中调用父类有参构造(初始化列表)

如果父类构造函数需要参数,子类必须在初始化列表中显式调用:

#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    string name;
    int age;

    Person(string n, int a)
        :name(n)
        ,age(a) 
    {
        cout << "Person 构造:" << name << ", " << age << endl;
    }
};

class Student : public Person {
public:
    int studentID;

    // 子类构造函数通过初始化列表调用父类有参构造
    Student(string n, int a, int id) 
        : Person(n, a)//关键点
        , studentID(id) 
    {
        cout << "Student 构造:学号=" << studentID << endl;
    }

    void show() {
        cout << "姓名:" << name << "  年龄:" << age
            << "  学号:" << studentID << endl;
    }
};

int main()
{
    Student stu("张三", 18, 1001);
    stu.show();
    return 0;
}

输出结果:

Person 构造:张三, 18
Student 构造:学号=1001
姓名:张三  年龄:18  学号:1001

四、函数重写(Override)

简介与作用

子类可以重新定义父类中已有的函数,这叫做函数重写(也叫覆盖)。重写后,通过子类对象调用该函数时,执行的是子类版本的函数。

关键点:

  • 函数名、参数列表、返回值类型必须与父类完全一致
  • 建议加上 override 关键字(C++11 起),让编译器帮你检查是否真的重写了父类函数,避免拼错函数名等低级错误。

💡 注意与**函数重载(Overload)**区分:重载是同一个类中同名不同参的多个函数;重写是子类对父类同名同参函数的重新定义。


代码示例

示例五:子类重写父类的方法
#include <iostream>
using namespace std;

class Shape {
public:
    void draw() 
    {
        cout << "画一个形状" << endl;
    }
};

class Circle : public Shape 
{
public:
    // 重写父类的 draw()
    void draw() 
    {
        cout << "画一个圆形 " << endl;
    }
};

class Rectangle : public Shape 
{
public:
    void draw() 
    {
        cout << "画一个矩形 " << endl;
    }
};

int main() {
    Shape s;
    s.draw();       // 调用父类版本

    Circle c;
    c.draw();       // 调用 Circle 的重写版本

    Rectangle r;
    r.draw();       // 调用 Rectangle 的重写版本

    return 0;
}

输出结果:

画一个形状
画一个圆形 ⭕
画一个矩形 ▭

示例六:使用 override 关键字(推荐写法)
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void speak() {  // virtual 表示这是虚函数,子类可以重写
        cout << "动物在叫..." << endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {  // override 明确告诉编译器:这是重写
        cout << "猫:喵喵喵~" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        cout << "狗:汪汪汪!" << endl;
    }
};

int main() {
    Cat cat;
    Dog dog;
    cat.speak();
    dog.speak();
    return 0;
}

输出结果:

猫:喵喵喵~
狗:汪汪汪!

📌 关于 virtual 虚函数与多态,将在后续文章中深入讲解,这里先有个印象即可。


⚠️ 经典 Bug:误以为重写但实际没有生效

class Base {
public:
    void func(int x) {
        cout << "Base::func(int)" << endl;
    }
};

class Derived : public Base {
public:
    // 注意:参数类型不同,这是【重载】而非【重写】!
    void func(double x) {
        cout << "Derived::func(double)" << endl;
    }
};

int main() {
    Derived d;
    d.func(1);   // 调用的是 Base::func(int),而非 Derived::func(double)!
    return 0;
}

解决方法:使用 override 关键字,如果重写不正确编译器会直接报错,帮你发现问题。


五、子类访问父类的同名成员

简介与作用

当子类定义了与父类同名的成员变量或成员函数时,子类成员会遮蔽(隐藏)父类同名成员。如果子类仍然需要访问父类的同名成员,需要加上作用域限定符 父类名::


代码示例

示例七:子类与父类同名成员的访问
#include <iostream>
using namespace std;

class Base {
public:
    int value = 100;

    void show() {
        cout << "Base::show(),value = " << value << endl;
    }
};

class Derived : public Base {
public:
    int value = 999;  // 与父类同名,会遮蔽父类的 value

    void show() {
        cout << "Derived::show(),value = " << value << endl;
    }

    void showAll() {
        cout << "子类 value = " << value << endl;
        cout << "父类 value = " << Base::value << endl;  // 用作用域限定符访问父类成员
        Base::show();  // 调用父类的 show()
    }
};

int main() {
    Derived d;
    d.show();         // 调用子类的 show()
    d.Base::show();   // 通过作用域限定符调用父类的 show()
    d.showAll();
    return 0;
}

输出结果:

Derived::show(),value = 999
Base::show(),value = 100
子类 value = 999
父类 value = 100
Base::show(),value = 100

六、多继承

简介与作用

C++ 支持多继承,即一个子类可以同时继承多个父类,获得多个父类的属性和方法。

语法:

class 子类 : public 父类A, public 父类B, ... { };

⚠️ 虽然 C++ 支持多继承,但实际工程中应谨慎使用,因为多继承容易引起"菱形继承"等复杂问题(见下文)。很多现代语言(如 Java、C#)甚至直接禁止了多继承。


代码示例

示例八:同时继承两个父类
#include <iostream>
using namespace std;

class Flyable {
public:
    void fly() {
        cout << "我会飞!" << endl;
    }
};

class Swimmable {
public:
    void swim() {
        cout << "我会游泳!" << endl;
    }
};

// Duck 同时继承 Flyable 和 Swimmable
class Duck : public Flyable, public Swimmable {
public:
    void quack() {
        cout << "嘎嘎嘎!" << endl;
    }
};

int main() {
    Duck duck;
    duck.fly();    // 来自 Flyable
    duck.swim();   // 来自 Swimmable
    duck.quack();  // Duck 自己的方法
    return 0;
}

输出结果:

我会飞!
我会游泳!
嘎嘎嘎!

⚠️ 经典 Bug:菱形继承的二义性问题

#include <iostream>
using namespace std;

class Animal {
public:
    int age = 5;
};

class Dog : public Animal {};
class Cat : public Animal {};

// 同时继承 Dog 和 Cat,而两者都继承自 Animal
// Animal 的成员被继承了两份!
class Hybrid : public Dog, public Cat {};

int main() {
    Hybrid h;
    // h.age = 10;  // ❌ 编译错误!age 有歧义,不知道是 Dog 的 age 还是 Cat 的 age
    h.Dog::age = 10;  // 必须指定作用域才能访问
    h.Cat::age = 20;
    cout << "Dog::age = " << h.Dog::age << endl;
    cout << "Cat::age = " << h.Cat::age << endl;
    return 0;
}

这种多条路径导致同一个基类被继承多次的情况,就叫做菱形继承(Diamond Inheritance)。解决方案是使用虚继承(见下文)。


七、虚继承

简介与作用

虚继承(Virtual Inheritance) 是解决菱形继承问题的专用手段。

使用虚继承后,无论通过多少条路径继承同一个基类,该基类的成员只保留一份,消除了二义性。

语法:在继承时加上 virtual 关键字:

class 中间类 : virtual public 基类 { };

代码示例

示例九:虚继承解决菱形继承问题
#include <iostream>
using namespace std;

class Animal {
public:
    int age = 5;
    void show() {
        cout << "Animal::age = " << age << endl;
    }
};

// 关键:Dog 和 Cat 使用虚继承
class Dog : virtual public Animal {};
class Cat : virtual public Animal {};

class Hybrid : public Dog, public Cat {};

int main() {
    Hybrid h;
    h.age = 10;    // ✅ 现在没有歧义了!Animal 只有一份
    h.show();      // ✅ 正常调用

    cout << "age = " << h.age << endl;
    return 0;
}

输出结果:

Animal::age = 10
age = 10

💡 加上 virtual 关键字后,Animal 的数据在 Hybrid 对象中只存储一份,Dog::age 和 Cat::age 指向同一块内存,从根本上解决了二义性问题。


八、综合案例:员工管理系统

最后,用一个小案例把本文的知识点串联起来,加深理解。

#include <iostream>
#include <string>
using namespace std;

// 基类:员工
class Employee {
protected:
    string name;
    int id;
    double baseSalary;

public:
    Employee(string n, int i, double s) : name(n), id(i), baseSalary(s) {}

    virtual void showInfo() {
        cout << "员工姓名:" << name
             << "  工号:" << id
             << "  基本工资:" << baseSalary << endl;
    }

    // 虚函数:计算工资(子类各自实现)
    virtual double calcSalary() {
        return baseSalary;
    }

    virtual ~Employee() {}  // 虚析构,良好习惯
};

// 派生类:普通员工(无奖金)
class NormalEmployee : public Employee {
public:
    NormalEmployee(string n, int i, double s) : Employee(n, i, s) {}

    void showInfo() override {
        cout << "[普通员工] ";
        Employee::showInfo();
        cout << "  实发工资:" << calcSalary() << endl;
    }

    double calcSalary() override {
        return baseSalary;
    }
};

// 派生类:销售员(有提成)
class SalesPerson : public Employee {
private:
    double commission;  // 提成

public:
    SalesPerson(string n, int i, double s, double c)
        : Employee(n, i, s), commission(c) {}

    void showInfo() override {
        cout << "[销售员工] ";
        Employee::showInfo();
        cout << "  提成:" << commission
             << "  实发工资:" << calcSalary() << endl;
    }

    double calcSalary() override {
        return baseSalary + commission;
    }
};

// 派生类:管理层(有年终奖)
class Manager : public Employee {
private:
    double bonus;  // 年终奖

public:
    Manager(string n, int i, double s, double b)
        : Employee(n, i, s), bonus(b) {}

    void showInfo() override {
        cout << "[管理人员] ";
        Employee::showInfo();
        cout << "  年终奖:" << bonus
             << "  实发工资:" << calcSalary() << endl;
    }

    double calcSalary() override {
        return baseSalary + bonus;
    }
};

int main() {
    NormalEmployee e1("小李", 1001, 5000);
    SalesPerson    e2("小王", 1002, 3000, 2000);
    Manager        e3("张总", 1003, 10000, 50000);

    cout << "======= 员工信息汇总 =======" << endl;
    e1.showInfo();
    cout << endl;
    e2.showInfo();
    cout << endl;
    e3.showInfo();

    return 0;
}

输出结果:

======= 员工信息汇总 =======
[普通员工] 员工姓名:小李  工号:1001  基本工资:5000
  实发工资:5000

[销售员工] 员工姓名:小王  工号:1002  基本工资:3000
  提成:2000  实发工资:5000

[管理人员] 员工姓名:张总  工号:1003  基本工资:10000
  年终奖:50000  实发工资:60000

📝 总结

知识点 核心要点
继承基本语法 class 子类 : public 父类 {}
三种访问方式 public / protected / private,日常用 public
父类 private 成员 子类永远无法直接访问
构造/析构顺序 构造:父→子;析构:子→父
调用父类有参构造 在子类构造函数的初始化列表中调用
函数重写 子类重新定义父类函数,建议加 override
同名成员访问 用 父类名::成员 指定作用域
多继承 一个子类继承多个父类,谨慎使用
菱形继承问题 同一基类被继承多次,产生二义性
虚继承 用 virtual 解决菱形继承,基类成员只保留一份

🚀 下期预告

掌握了继承,就为学习多态打下了坚实基础。下一篇文章,我们将深入讲解 C++ 中的多态机制:虚函数、纯虚函数、抽象类……让继承的威力真正发挥出来!


如果本文对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注,你的支持是我继续创作的动力! 有任何问题欢迎在评论区留言,我会一一解答 😊

更多推荐