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

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

C++ 继承是面向对象编程的核心复用手段,通过基类与派生类的关系实现代码扩展。本文涵盖继承定义、访问权限控制、作用域隐藏规则及默认成员函数生成机制。重点解析多继承带来的数据冗余与二义性问题,并介绍虚继承解决方案。最后对比继承与组合的优劣,强调优先使用组合的设计原则。

游戏玩家发布于 2026/3/30更新于 2026/9/1254 浏览
C++ 继承机制详解:从基础到多继承

C++ 继承机制详解

继承(inheritance)是面向对象程序设计中代码复用的核心手段。它允许我们在保持原有类特征的基础上进行扩展,增加方法或属性,从而产生新的类——派生类。这种机制体现了由简单到复杂的认知过程,实现了类设计层次的复用。

1. 继承的概念及定义

1.1 概念理解

在引入继承之前,如果我们设计 Student 和 Teacher 两个类,它们都包含姓名、地址、电话等公共成员,以及各自独有的身份认证逻辑。直接复制这些公共成员会导致代码冗余。

通过提取公共部分为 Person 基类,让 Student 和 Teacher 继承自 Person,即可复用这些成员,大幅提升代码的简洁性和可维护性。

class Person {
public:
    void identity() { cout << "void identity()" << _name << endl; }
protected:
    string _name = "张三";
    string _address;
    string _tel;
    int _age = 19;
};

class Student : public Person {
public:
    void study() {}
protected:
    int _num; // 学号
};

class Teacher : public Person {
public:
    void teaching() {}
protected:
    string title;
};

int main() {
    Student s;
    Teacher t;
    s.identity();
    t.identity();
    return 0;
}

1.2 继承定义与访问权限

基类(父类)的成员在派生类中的可见性取决于继承方式。下表总结了不同继承方式下成员访问权限的变化:

类成员/继承方式public 继承protected 继承private 继承
基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员
基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员
基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见

几点关键说明:

  1. 私有成员不可见:基类的 private 成员无论以何种方式继承,在派生类中都是不可见的。这并不意味着数据不存在于对象中,而是语法上限制了访问。
  2. 保护成员的作用:如果希望基类成员不在类外被访问,但需要在派生类中访问,应定义为 protected。
  3. 默认继承方式:使用关键字 class 时默认为 private 继承,使用 struct 时默认为 public 继承。实际开发中建议显式写出继承方式。
  4. 最佳实践:实际运用中一般使用 public 继承。protected 或 private 继承会限制成员的可用性,降低扩展性和维护性。
class Person {
public:
    void Print() { cout << _name << endl; }
protected:
    string _name;
private:
    int _age;
};

class Student : public Person {
protected:
    int _num;
};

1.3 继承类模板

当基类是类模板时,派生类实例化需要指定作用域,否则可能因模板按需实例化导致找不到标识符。

namespace Achieve {
    template<class T>
    class stack : public vector<T> {
    public:
        void push(const T& x) {
            // 必须指定作用域,因为 vector<T>::push_back 未实例化时找不到
            vector<T>::push_back(x);
        }
        void pop() { vector<T>::pop_back(); }
        const T& top() { return vector<T>::back(); }
        bool empty() { return vector<T>::empty(); }
    };
}

int main() {
    Achieve::stack<int> st;
    st.push(1);
    st.push(2);
    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

2. 基类和派生类间的转换

  • 向上转型:public 继承的派生类对象可以赋值给基类的指针或引用。这被称为'切片',即只保留派生类中基类的那部分。
  • 向下转换:基类对象不能直接赋值给派生类对象。基类指针或引用可以通过强制类型转换指向派生类对象,但需确保基类指针实际指向的是派生类对象。若涉及多态模型,建议使用 RTTI(如 dynamic_cast)进行安全转换。
class Person {
protected:
    string _name;
    string _sex;
    int _age;
};

class Student : public Person {
public:
    int _num;
};

int main() {
    Student sobj;
    Person* pp = &sobj;      // 子类对象转父类指针
    Person& rp = sobj;       // 子类对象转父类引用
    
    Person pobj = sobj;      // 切片操作
    // sobj = pobj;         // 编译报错:父类对象不能赋值给派生类对象
    return 0;
}

3. 继承中的作用域

3.1 隐藏规则

在继承体系中,父类和子类拥有独立的作用域。若子类中存在与父类同名的成员,子类成员将屏蔽父类同名成员的直接访问,这种现象称为隐藏。

  • 成员函数隐藏只需函数名相同。
  • 在子类成员函数中,可以使用 父类::成员名 显式访问被隐藏的父类成员。
  • 实际开发中应避免定义同名成员,以免混淆。
class Person {
protected:
    string _name = "张三";
    int _num = 111;
};

class Student : public Person {
public:
    void Print() {
        cout << "姓名:" << _name << endl;
        cout << "身份证号:" << Person::_num << endl; // 显式访问
        cout << "学号:" << _num << endl;
    }
protected:
    int _num = 111; // 隐藏了 Person::_num
};

int main() {
    Student s;
    s.Print();
    return 0;
}

3.2 常见考点

  • 重载 vs 隐藏:若子类函数名与父类不同但参数不同,通常构成重载;若仅函数名相同,则构成隐藏。
  • 调用解析:调用 b.fun() 时,若子类定义了同名函数,优先调用子类的版本,除非显式指定作用域。

4. 派生类的默认成员函数

编译器会自动生成默认成员函数,但在继承场景下,其行为有特殊规则:

  1. 构造函数:派生类构造函数必须调用基类构造函数初始化基类部分。若基类无默认构造函数,必须在初始化列表中显式调用。
  2. 拷贝构造:派生类拷贝构造需调用基类拷贝构造完成基类部分的拷贝。
  3. 赋值运算符:派生类 operator= 会隐藏基类的 operator=。若需调用基类版本,需指定基类作用域。
  4. 析构函数:派生类析构完成后自动调用基类析构。清理顺序遵循'先派生后基类'的原则,确保资源正确释放。
  5. 虚析构:在多态场景中,基类析构函数应声明为 virtual,避免对象销毁时发生内存泄漏。
class Person {
public:
    Person(const string s = "张三") : _name(s) {}
    Person(const Person& p) : _name(p._name) {}
    Person& operator=(const Person& p) {
        if (this != &p) _name = p._name;
        return *this;
    }
protected:
    string _name;
};

class Student : public Person {
public:
    Student(const string& p, int num) : Person(p), _num(num) {}
    Student(const Student& s) : Person(s._name), _num(s._num) {}
    Student& operator=(const Student& s) {
        if (this != &s) {
            Person::operator=(s); // 显式调用基类赋值
            _num = s._num;
        }
        return *this;
    }
private:
    int _num = 1;
};

4.2 实现不能被继承的类

  • 法一:将基类构造函数设为私有,派生类无法访问,从而无法实例化。
  • 法二:C++11 引入 final 关键字修饰基类,禁止后续继承。
class Base final {
public:
    void func() { cout << "Base::func()" << endl; }
protected:
    int a = 1;
};
// class Derive : public Base { ... }; // 编译报错

5. 继承与友元

友元关系不具备继承性。基类的友元不能访问派生类的私有和保护成员。若需访问,需在派生类中重新声明友元。

6. 继承与静态函数

静态成员在整个继承体系中只有一个实例。无论派生出多少类,所有类共享同一份静态成员数据。

class Person {
public:
    string _name;
    static int _count;
};
int Person::_count = 0;

class Student : public Person {
protected:
    int _stuNum;
};

int main() {
    Person p;
    Student s;
    cout << &p._name << endl; // 非静态成员地址不同
    cout << &s._name << endl;
    cout << &p._count << endl; // 静态成员地址相同
    cout << &s._count << endl;
    return 0;
}

7. 多继承及其菱形继承问题

7.1 继承模型

  • 单继承:一个派生类只有一个直接基类。
  • 多继承:一个派生类有两个或以上直接基类。内存布局上,先继承的基类在前,后继承的在后,派生类成员在最后。
  • 菱形继承:多继承的特殊情况,存在数据冗余和二义性问题。例如 Assistant 同时继承 Student 和 Teacher,而两者都继承 Person,导致 Assistant 中包含两份 Person 数据。
class Person {
public:
    string _name;
};

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 a;
    a._name = "peter"; // 二义性错误
    // 解决二义性需指定作用域,但无法解决数据冗余
    a.Student::_name = "xxx";
    a.Teacher::_name = "yyy";
    return 0;
}

7.2 虚继承

为解决菱形继承的数据冗余问题,可使用 virtual 关键字进行虚继承。底层实现较复杂,性能有损耗,因此实践中不建议过度设计菱形继承结构。

class Person {
public:
    Person(const string& name) : _name(name) {}
protected:
    string _name;
};

class Student : virtual public Person {
public:
    Student(const string& name, int num) : Person(name), _num(num) {}
protected:
    int _num;
};

class Teacher : virtual public Person {
public:
    Teacher(const string& name, int id) : Person(name), _id(id) {}
protected:
    int _id;
};

class Assistant : public Student, public Teacher {
public:
    Assistant(const string& n1, const string& n2, const string& n3)
        : Person(n3), Student(n1, 1), Teacher(n2, 2) {}
protected:
    string _majorCourse;
};

int main() {
    Assistant a("张三", "李四", "王五");
    return 0;
}

7.3 多继承指针偏移

在多继承中,不同基类指针的地址可能不同。例如 Derive 继承 Base1 和 Base2,Base1* 和 Base2* 指向同一对象时,其内部偏移量不同。

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 {
public: int _d;
};

int main() {
    Derive d;
    Base1* p1 = &d;
    Base2* p2 = &d;
    Derive* p3 = &d;
    // p1 == p3 != p2
    return 0;
}

8. 继承和组合

  • 继承:是一种 is-a 的关系。派生类对象本质上也是基类对象。属于白箱复用,耦合度高,基类改变会影响派生类。
  • 组合:是一种 has-a 的关系。新对象包含另一个对象作为成员。属于黑箱复用,耦合度低,接口定义良好。

设计原则:优先使用组合而非继承。组合能更好地封装细节,降低依赖。仅在明确满足 is-a 关系或需要实现多态时才考虑继承。

目录

  1. C++ 继承机制详解
  2. 1. 继承的概念及定义
  3. 1.1 概念理解
  4. 1.2 继承定义与访问权限
  5. 1.3 继承类模板
  6. 2. 基类和派生类间的转换
  7. 3. 继承中的作用域
  8. 3.1 隐藏规则
  9. 3.2 常见考点
  10. 4. 派生类的默认成员函数
  11. 4.2 实现不能被继承的类
  12. 5. 继承与友元
  13. 6. 继承与静态函数
  14. 7. 多继承及其菱形继承问题
  15. 7.1 继承模型
  16. 7.2 虚继承
  17. 7.3 多继承指针偏移
  18. 8. 继承和组合

更多推荐文章

查看全部
  • Linux 系统 Git 安装与基础配置指南
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具实战
  • 国内公共安全领域首个警用 AI 助手“天擎”发布
  • Flutter 三方库 eth_sig_util 的鸿蒙化适配指南
  • Qwen3 与 Qwen Agent 智能体开发实战:接入 MCP 工具
  • Flutter 组件 tavily_dart 在鸿蒙 Harmony 上的适配与 AI 搜索集成实战
  • MuGo 源码逐行解读:从特征提取到蒙特卡洛树搜索
  • 前端开发:避免重复造轮子,选用成熟组件库
  • Microi 吾码:低代码解锁服务器虚拟化的无限潜能
  • Raphael AI:基于 Flux 模型的免费 AI 图像生成工具
  • HarmonyOS 6.0 OAID 服务正式支持 TV 设备
  • 从零开始学 AI 绘画:麦橘超然部署与实战指南
  • 前端 PDF 导出实战:JSPDF 与 HTML2Canvas 深度解析
  • Git 版本控制系统基础与核心命令详解
  • Python 驱动 Ksycopg2 连接和使用 Kingbase 数据库实战
  • DuckX:C++操作 Word 文档的完整指南
  • 《Agent Runtime 工程化》第八章 Checkpoint、Resume、Rollback:8.6 rollback 不是 undo 按钮
  • 力扣第 1 题:两数之和(C 语言实现)
  • AIGC 时代 C++ 突破推理吞吐瓶颈的 3 大核心技术
  • Linux 与 Windows 系统 traceroute 与 tracert 命令详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • 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