跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 继承机制详解:从基础概念到多继承实战

综述由AI生成C++ 继承是面向对象的核心特性,允许派生类复用基类代码并扩展功能。详细讲解了继承的定义与访问控制规则,包括 public/protected/private 继承对成员可见性的影响。内容涵盖作用域隐藏、默认成员函数的生成与调用顺序、虚继承解决菱形继承的数据冗余问题,以及多继承中的指针偏移现象。此外,还对比了继承与组合的区别,强调优先使用组合以降低耦合度。通过代码示例演示了模板继承、友元关系及静态成员在继承体系中的行为,帮助开发者构建可维护的类体系。

abccba发布于 2026/3/16更新于 2026/4/265 浏览
C++ 继承机制详解:从基础概念到多继承实战

C++ 继承机制详解

一、继承的概念及定义

1.1 继承的概念

继承(Inheritance)是面向对象编程中代码复用的核心手段。它允许我们在已有类的基础上创建新类,在保持原有特性的同时扩展功能。通过继承构建的层次结构,能让代码更清晰、易维护。

举个例子,如果没有继承,Student 和 Teacher 类都需要重复定义姓名、年龄等成员变量。引入继承后,我们可以将公共部分提取为 Person 基类,让子类复用这些逻辑。

class Person {
public:
    // 进入校园/图书馆/实验室刷二维码等身份认证
    void identity() {
        cout << "void identity()" << _name << endl;
    }
protected:
    string _name = "张三"; // 姓名
    string _address;       // 地址
    string _tel;           // 电话
    int _age = 18;         // 年龄
};

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

class Teacher : public Person {
public:
    // 授课
    void teaching() {
        // ...
    }
protected:
    string title; // 职称
};

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

1.2 继承的定义

1.2.1 定义格式

基类(父类)与派生类(子类)的关系通过继承方式建立。C++ 支持三种继承方式:public、protected 和 private。它们决定了基类成员在派生类中的访问权限变化。

注意:private 和 protected 在同一个类内作用相似,但在继承时效果截然不同,直接影响派生类的访问能力。

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

理解这张表的关键点:

  1. 基类 private 成员在派生类中始终不可见,但物理上仍存在于对象内存中。
  2. 若希望基类成员不被外部直接访问,但允许派生类访问,应使用 protected。
  3. 实际开发中,绝大多数情况使用 public 继承。protected/private 继承会限制成员的可用性,降低扩展性。
  4. 默认情况下,class 关键字对应 private 继承,struct 对应 public 继承,建议显式声明。

1.3 继承类模板

容器适配器如 stack 底层通常基于 vector。我们可以尝试用 stack 继承 vector,但这涉及模板实例化的细节。

namespace sxn {
template<class T>
class stack : public std::vector<T> {
public:
    void push(const T& x) {
        // 基类是类模板时,需要指定一下类域
        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() {
    sxn::stack<int> st;
    st.push(1);
    st.push(2);
    st.push(3);
    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

为什么需要指定类域? 类模板是按需实例化的。当定义 stack 时,编译器只检查语法。只有当实际使用 stack<int> 并调用 push_back 时,才会实例化 vector<int> 的相关代码。如果未指定作用域,编译器可能找不到 push_back 标识符,导致编译错误。

二、基类和派生类间的转化

  1. 派生类对象 -> 基类指针/引用:这是安全的,称为'切片'或'切割'。基类指针指向的是派生类对象中属于基类的那部分数据。
  2. 基类对象 -> 派生类对象:不允许直接赋值,因为派生类包含更多成员。
  3. 强制转换:基类指针可转为派生类指针,但必须确保该指针实际指向的是派生类对象。对于多态类型,推荐使用 RTTI 的 dynamic_cast 进行安全转换。

继承模型示意图

class Person {
protected:
    string _name; // 姓名
    string _sex;  // 性别
    int _age;     // 年龄
};

class Student : public Person {
public:
    int _No; // 学号
};

int main() {
    Student sobj;
    // 1. 派生类对象可以赋值给基类的指针/引用
    Person* pp = &sobj;
    Person& rp = sobj;
    
    // 2. 基类对象不能赋值给派生类对象,这里会编译报错
    // sobj = pobj;
    return 0;
}

三、继承中的作用域

3.1 隐藏规则

  1. 基类和派生类拥有独立的作用域。
  2. 若两者存在同名成员,派生类成员将屏蔽基类同名成员的直接访问,这叫隐藏。
  3. 函数名相同即构成隐藏(区别于重载)。在派生类中可通过 基类::成员名 显式访问被隐藏的基类成员。
  4. 实际开发中,尽量避免在继承体系中定义同名成员,以免混淆。
// Student 的_num 和 Person 的_num 构成隐藏关系
class Person {
protected:
    string _name = "小雷子"; // 姓名
    int _num = 072;          // 身份证号
};

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

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

3.2 考察继承作用域相关选择题

问题: 下面 A 和 B 类中的两个 func 构成什么关系? A. 重载 B. 隐藏 C. 没关系

解析: 重载发生在同一作用域内。基类和派生类是两个不同作用域,且函数名相同,因此是隐藏关系。

问题: 下面程序的编译运行结果是什么?

class A {
public:
    void fun() { cout << "func()" << endl; }
};
class B : public A {
public:
    void fun(int i) { cout << "func(int i)" << i << endl; }
};
int main() {
    B b;
    b.fun(10); // 调用派生类 fun(int)
    b.fun();   // 试图调用基类 fun(),但被隐藏
    return 0;
};

答案: 编译报错。虽然 b.fun() 语义上想调用基类函数,但由于派生类定义了同名的 fun,基类版本被隐藏。编译器在语义分析时发现调用不匹配,直接报错。

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

派生类会自动生成 6 个默认成员函数,其行为如下:

  1. 构造函数:必须调用基类构造函数初始化基类部分。若基类无默认构造,需在初始化列表中显式调用。
  2. 拷贝构造函数:需调用基类拷贝构造完成基类部分的拷贝。
  3. 赋值运算符:需调用基类赋值运算符。注意派生类 operator= 可能隐藏基类版本,需指定作用域调用。
  4. 析构函数:先执行派生类析构,再自动调用基类析构,确保清理顺序正确。
  5. 初始化顺序:先基类构造,后派生类构造;析构则相反。
  6. 虚析构:在多态场景下,基类析构函数建议加 virtual,否则派生类析构可能与基类构成隐藏而非重写。

示例演示:

class Person {
public:
    Person(const char* name = "joke") : _name(name) {
        cout << "Person()" << endl;
    }
    Person(const Person& p) : _name(p._name) {
        cout << "Person(const Person& p)" << endl;
    }
    Person& operator=(const Person& p) {
        cout << "Person operator=(const Person& p)" << endl;
        if (this != &p) _name = p._name;
        return *this;
    }
    ~Person() {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};

class Student : public Person {
public:
    Student(const char* name, int num) : Person(name), _num(num) {
        cout << "Student()" << endl;
    }
    Student(const Student& s) : Person(s), _num(s._num) {
        cout << "Student(const Student& s)" << endl;
    }
    Student& operator=(const Student& s) {
        cout << "Student& operator= (const Student& s)" << endl;
        if (this != &s) {
            Person::operator=(s); // 显式调用基类
            _num = s._num;
        }
        return *this;
    }
    ~Student() {
        cout << "~Student()" << endl;
    }
protected:
    int _num; // 学号
};

int main() {
    Student s1("jack", 18);
    Student s2(s1);
    Student s3("rose", 17);
    s1 = s3;
    return 0;
}

4.2 实现一个不能被继承的类

方法 1:将基类构造函数设为私有,派生类无法访问。 方法 2:C++11 引入 final 关键字修饰基类。

class Base final {
public:
    void func5() { cout << "Base::func5" << endl; }
protected:
    int a = 1;
};

class Derive : public Base { // 编译错误:Derive 不能继承 final 类
    void func4() { cout << "Derive::func4" << endl; }
    protected:
    int b = 2;
};

五、继承与友元

友元关系不能继承。基类的友元无法访问派生类的私有和保护成员。

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

class Student;
class Person {
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name; // 姓名
};

class Student : public Person {
protected:
    int _stuNum; // 学号
};

void Display(const Person& p, const Student& s) {
    cout << p._name << endl;
    cout << s._stuNum << endl; // 错误:Display 不是 Student 的友元
}

int main() {
    Person p;
    Student s;
    Display(p, s);
    return 0;
}

六、继承与静态成员

基类定义的 static 静态成员在整个继承体系中只有一个实例。无论派生出多少个派生类,都共享同一个静态成员。

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

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;
    cout << Person::_count << endl;
    cout << Student::_count << endl;
    return 0;
}

七、多继承及菱形继承问题

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"; // 编译报错:对_name 的访问不明确
    return 0;
}

解决二义性需显式指定:a.Student::_name = "xxx";,但这无法解决数据冗余。

7.2 虚继承

引入 virtual 关键字可解决菱形继承问题,确保基类在派生类中只有一份副本。

class Person {
public:
    string _name; // 姓名
};

class Student : virtual public Person {
protected:
    int _num; // 学号
};

class Teacher : virtual public Person {
protected:
    int _id; // 职工编号
};

class Assistant : public Student, public Teacher {
protected:
    string _majorCourse; // 主修课程
};

int main() {
    Assistant a;
    a._name = "bob"; // 正常访问,无二义性
    return 0;
}

注意:虚继承会增加底层实现的复杂度及性能开销,除非必要,不建议随意设计菱形继承结构。

7.3 多继承中指针偏移问题

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;
}

八、继承和组合

  1. 继承是 is-a 关系,派生类对象也是基类对象。属于白箱复用,耦合度高。
  2. 组合是 has-a 关系,对象中包含另一个对象。属于黑箱复用,耦合度低。
  3. 原则:优先使用组合,而非继承。组合能更好地封装内部细节,降低依赖。当然,若确实需要多态或明确的 is-a 关系,继承仍是必要的。

就像 stack 继承 vector 一样,标准库也提供了组合的实现方式供选择。

总结

继承为后续学习多态打下了坚实基础,是深入理解 C++ 面向对象编程的关键一步。合理运用继承机制,能够让我们设计出更加优雅、可扩展的代码结构。在实际开发中,要注意避免过度继承带来的复杂性和维护成本,优先考虑组合模式。

目录

  1. C++ 继承机制详解
  2. 一、继承的概念及定义
  3. 1.1 继承的概念
  4. 1.2 继承的定义
  5. 1.2.1 定义格式
  6. 1.2.2 继承基类成员访问方式的变化
  7. 1.3 继承类模板
  8. 二、基类和派生类间的转化
  9. 三、继承中的作用域
  10. 3.1 隐藏规则
  11. 3.2 考察继承作用域相关选择题
  12. 四、派生类的默认成员函数
  13. 4.2 实现一个不能被继承的类
  14. 五、继承与友元
  15. 六、继承与静态成员
  16. 七、多继承及菱形继承问题
  17. 7.1 继承模型
  18. 7.2 虚继承
  19. 7.3 多继承中指针偏移问题
  20. 八、继承和组合
  21. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • RTX 5060 Ti Linux 驱动黑屏解决方案:BIOS 设置与 Open Kernel 配置
  • Linux poll 多路复用详解:select 的改进与局限
  • Python 全套学习路线:基础、进阶与标准库实战指南
  • AutoGPT 与 Python:构建自主 AI 智能体的实战指南
  • Java 分治算法实战:快速排序与归并排序
  • Meta Llama 3.1 70B 与 Mistral Large 2 128B 深度对比
  • 深入理解 AI 编程中的 Skills:定义、用法与 Java 实战
  • 基于 AirSim 的无人机深度强化学习路径规划与避障
  • 2026 GitHub 热门 Python 项目:AI 代理与数据工具精选
  • EasyAI:Java 程序员的人工智能算法框架
  • Web Animations API (WAAPI) 核心原理与实战应用
  • Git 安装与配置完整指南
  • 无人机视觉任务常用数据集汇总(检测与分割)
  • Windows 11 配置 CUDA 版 llama.cpp 实现本地大模型离线聊天
  • GitHub Copilot 复用 Claude Code 本地技能的自动化方案
  • Microi 吾码:基于 Spring Boot 的低代码微服务框架与表单引擎
  • Python 爬虫入门实战:项目驱动与核心原理
  • iOS 26 系统兼容适配:UITabBar 液态玻璃效果与 WiFi SSID 获取
  • AI 大模型行业现状、应用场景及产业链分析
  • GESP-C++四级考试核心知识点与编程模板

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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