跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 继承机制详解:从基础到多态应用

本文深入解析 C++ 继承机制,涵盖基类与派生类关系、访问控制、对象切片、作用域隐藏、默认成员函数(构造与析构)、友元及静态成员特性。重点阐述了多继承带来的菱形问题及其虚拟继承解决方案,并对比了继承与组合的适用场景。通过代码示例说明了 is-a 与 has-a 的设计原则,帮助开发者掌握面向对象设计的核心要点。

协议工匠发布于 2026/3/22更新于 2026/10/23100 浏览
C++ 继承机制详解:从基础到多态应用

一、概念

继承是类设计层次复用的核心手段。通过继承,子类可以复用基类的成员,同时扩展新功能。

语法示例:

class Person { /* 基类 */ };
class Student : public Person { /* 派生类 */ };

继承关系与访问限定符紧密相关。继承后,基类成员的可见性会发生变化:

  1. 私有成员:虽然被继承到了派生类对象中,但语法上限制派生类(无论内部还是外部)都无法直接访问。它们在基类内可用,在基类外不可见。
  2. 保护成员:如果希望基类成员不在类外直接访问,但在派生类中可访问,应定义为 protected。
  3. 访问权限计算:基类其他成员在子类的访问方式遵循规则:Min(成员在基类的访问限定符,继承方式)。即 public > protected > private。

二、基类和派生类对象赋值转换

不同类型对象赋值需要类型转换,但继承体系中有特殊规则。

父类对象不能赋值给子类对象,强转也不行。否则子类特有的成员变量怎么办?给随机值吗?

Person p; Student s;
s = p; // 错误:父类不能赋值给子类
// s = (Student)p; // 强转也不行
p = s; // 正确

子类对象可以赋值给父类的对象、指针、引用。 当子类赋值给父类对象时,语法上特殊处理,没有发生类型转换,而是赋值兼容(切片)。把子类里属于父类的那部分切出来,拷贝给父类。

怎么证明没有发生类型转换?可以用别名验证。如果把父类的成员变量放成 public,可以看到 p2 变成了子类中父类那一部分的别名,而非整个子类对象。

三、继承中的作用域

作用域查找是编译时的规则。定义了一个类就有类域,子类和父类有独立的类域。

同名成员隐藏:子类成员将屏蔽父类对同名成员的直接访问,这叫隐藏(重定义)。在子类成员函数中,可以使用 父类::父类成员 显式访问。

现实中尽量不要在继承体系里定义同名成员,这容易造成混淆。

class Person {
public:
    void fun() { cout << "Person::func()" << endl; }
protected:
    string _name = "小李子";
    int _num = 111;
};

class Student : public Person {
public:
    void fun(int) { cout << "Student::func(int)" << endl; } // 隐藏了父类的 fun()
    void Print() {
        cout << "姓名:" << _name << endl;
        cout << _num << endl;          // 999,子类成员
        cout << Person::_num << endl;   // 111,显式访问父类
    }
protected:
    int _num = 999;
};

int main() {
    Student s;
    s.Print();
    s.fun(1);      // 调用子类版本
    s.Person::fun(); // 显式调用父类版本
    return 0;
}

注意:同一作用域才能构成重载。这里两个 fun 只是隐藏关系,不是重载。

四、派生类的默认成员函数

设计理念:父干父的活,子干子的活。

1. 构造函数

子类必须调父类的构造函数来初始化父类成员。默认情况下,在初始化列表自动调用父类的默认构造函数。若父类不提供默认构造函数,则必须手动指定。

规定:子类的构造函数中,不能在初始化列表显示初始化父类成员(因为那是基类部分),只能在构造函数体内初始化,或者在初始化列表中调用基类构造函数。

class Person {
public:
    Person(const char* name) : _name(name) { cout << "Person()" << endl; }
    ~Person() { cout << "~Person()" << endl; }
protected:
    string _name;
};

class Student : public Person {
public:
    // 必须在初始化列表中调用基类构造函数
    Student(const char* name, int id) : Person(name), _id(id) {}
protected:
    int _id;
};

调试发现,在初始化列表先走 Person(name),说明继承的成员声明在自己的成员之前。初始化顺序是按声明顺序来的。

2. 拷贝构造

必须调父类的拷贝构造。子类对象可以传给父类对象的指针/引用,所以可以直接传子类对象给父类拷贝构造。

class Student : public Person {
public:
    Student(const Student& s) : Person(s), _id(s._id) {}
    // ... 省略其他代码
};

如果删去父类拷贝构造调用,默认不会调拷贝构造函数,而是调默认构造函数,这会导致数据不一致。

3. 赋值重载

class Student : public Person {
public:
    Student& operator=(const Student& s) {
        if (this != &s) {
            Person::operator=(s); // 显式调用父类赋值,避免隐藏
            _id = s._id;
        }
        return *this;
    }
};

4. 析构函数

构造、拷贝、赋值要显示调用。析构函数是个特殊,为保证析构顺序,会自动调。

为什么不能手动调用父类析构? 由于多态的原因,析构函数的函数名被特殊处理了。如果手动调用 Person::~Person(),可能会导致重复析构或顺序错误。总共 2 个对象,调了 4 次析构函数就是典型的错误示范。

正确的做法:子类析构函数完成后,系统自动调用父类析构,这样就保证了先子后父的析构顺序。为什么要保证先子后父?因为子类中有可能用到父类成员,父类不可能用到子类。如果先析构父类,子类再访问父类成员就会出错。

五、继承与友元

友元关系不能继承。父类友元不能访问子类私有和保护成员。

class Student;
class Person {
    friend void Display(const Person& p, const Student& s);
protected:
    string _name;
};
class Student : public Person {
    friend void Display(const Person& p, const Student& s);
protected:
    int _stuNum;
};
void Display(const Person& p, const Student& s) {
    cout << p._name << endl;
    cout << s._stuNum << endl; // 需要 Student 也声明友元
}

屏蔽掉 Student 中的友元声明会报错,因为友元关系不具备传递性。

六、继承与静态成员

父类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个 static 成员实例,在子类中不会单独拷贝一份。

class Person {
public:
    Person() { ++_count; }
    static int _count;
};
int Person::_count = 0;

class Student : public Person {};
// ... 使用逻辑同上

七、多继承、菱形继承、菱形虚拟继承

实践中可以谨慎用多继承,不要用菱形继承。

单继承:一个子类只有一个直接父类。 多继承:一个子类有两个或以上直接父类。 菱形继承:是多继承的一种特殊情况,会导致数据冗余和二义性。

class Person { public: string _name; int _age; };
class Student : public Person { protected: int _num; };
class Teacher : public Person { protected: int _id; };
class Assistant : public Student, public Teacher { protected: string _majorCourse; };

int main() {
    Assistant as;
    as._age = 19; // 报错:对'_age'的访问不明确
    return 0;
}

二义性可以通过指定访问路径解决(如 as.Student::_age),但这违背常理且维护困难。

虚拟继承解决了数据冗余和二义性问题。

class Student : virtual public Person { ... };
class Teacher : virtual public Person { ... };

这样设计后,Assistant 中只有一份 Person 的副本。原理是通过虚基表(vptr/vtable)机制,先取到偏移量,计算 _age 在对象中的地址,再访问。

八、继承和组合

class C {}; 
class D : public C {}; // 继承
class E { private: C _cc; }; // 组合

继承:白箱复用,父类的内部细节对子类可见;耦合度高。改变父类的成员,子类要跟着改。 组合:黑箱复用,对象的内部细节不可见;耦合度低。

实践中多用组合。继承和组合都可以,就用组合;更适合用继承的用继承;实现多态,必须用继承。

  • public 继承是 is-a 的关系:每个子类对象一定是父类对象(如教师、学生都是人)。
  • 组合是 has-a 的关系:E 组合了 C,每个 E 对象中都有一个 C 对象(如车有轮胎)。

以上就是 C++ 继承机制的核心内容。理解这些概念对于编写健壮、可扩展的代码至关重要。

目录

  1. 一、概念
  2. 二、基类和派生类对象赋值转换
  3. 三、继承中的作用域
  4. 四、派生类的默认成员函数
  5. 1. 构造函数
  6. 2. 拷贝构造
  7. 3. 赋值重载
  8. 4. 析构函数
  9. 五、继承与友元
  10. 六、继承与静态成员
  11. 七、多继承、菱形继承、菱形虚拟继承
  12. 八、继承和组合

更多推荐文章

查看全部
  • LlamaFactory v0.9.4 正式发布:全面升级的 LLM 微调框架
  • Diffusion Transformer(DiT):用 ViT 替代 U-Net 用于视频生成与机器人动作预测
  • JS 逆向:PEDATA 资讯接口加密解密与 zlib.gunzipSync 应用
  • 自然语言处理在客户服务领域的应用与实战
  • Vheer:免费免登录的 AI 绘画与视频生成工具
  • AI一键生成爆款短视频!2026最强提示词模板+实战案例
  • Robot Lab 基于 Isaac Lab 的机器人强化学习实战指南
  • 近端策略优化算法 (PPO) 详解与 PyTorch 实现
  • Git 入门指南:从零配置到 GitHub 上传实战
  • Transformer 三种注意力机制详解与 PyTorch 实现
  • 2026 年 2 月 AIGC 行业模型发布及前沿资讯汇总
  • Python YAML 模块实战:接口测试参数存储与配置
  • Java 集成高德开放平台 Web API 实践:POI 搜索 2.0 示例
  • 人工智能入门指南:零基础学习与实践
  • 使用 llama.cpp 合并 GGUF 模型分片
  • 2026 年 AI 大语言模型评测:GPT-5.2、Claude 4.5 与国产模型对比
  • Python 实现 MCP 客户端调用高德地图天气查询示例
  • 构建自动优化 Prompt 质量的元提示指南
  • Linux 动态链接与动态库加载深度解析
  • 漏洞挖掘入门教程:从零基础到精通的完整指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online