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

C++ 继承机制详解

深入解析 C++ 继承机制,涵盖概念定义、访问控制规则、类型转换与切片现象、作用域隐藏、默认成员函数调用顺序等核心知识点。重点阐述了多继承带来的菱形继承问题及其虚拟继承解决方案,对比了继承与组合的设计差异,强调优先使用组合以降低耦合度。内容包含典型代码示例,帮助理解内存布局与指针偏移细节。

信号故障发布于 2026/3/29更新于 2026/4/251 浏览
C++ 继承机制详解

继承的概念及定义

继承(Inheritance)是面向对象编程中实现代码复用的核心手段。它允许在已有类的基础上扩展新特性,添加成员函数和属性,从而生成新的类。原来的类称为基类(父类),新类称为派生类(子类)。相比函数层次的复用,继承实现了类设计层面的复用,有助于构建清晰的层次结构。

1.1 继承的定义格式

继承方式有三种:public、protected 和 private。这与访问限定符类似,但作用范围不同。

文章配图

1.2 成员访问权限的变化

继承方式会改变基类成员在派生类中的可见性。具体规则如下:

文章配图

文章配图

关键规则:

  1. 私有成员不可见:基类的 private 成员无论以何种方式继承,在派生类中都是不可见的。虽然它们被物理继承了,但语法上禁止直接访问。如果需要在派生类中使用,通常通过基类的公有接口暴露。
  2. 保护成员:若基类成员不希望被类外直接访问,但需在派生类中访问,应定义为 protected。
  3. 访问权限计算:派生类中成员的最终访问权限 = Min(基类成员访问限定符,继承方式)。顺序为 public > protected > private。
  4. 默认继承方式:使用 class 关键字时默认为 private 继承,使用 struct 时默认为 public 继承。实际开发中建议显式指定继承方式。

注意:实际应用中几乎都使用 public 继承。protected/private 继承会导致成员只能在派生类内部使用,扩展性和维护性较差,不推荐使用。

示例:继承与访问控制
class Person {
public:
    void identity() { /* ... */ }
    // 提供公有接口访问私有成员
    void func() { ++_age; }
protected:
    string _name = ;
    string _address;
    string _tel;
:
     _age = ;
};

  :  Person {
:
    {
        _name = ; 
        
        (); 
    }
:
     _stuid;
};

{
    Student s;
    s.(); 
    s.();     
     ;
}
"peter"
private
int
18
class
Student
public
public
void study()
"张三"
// 可访问 protected 成员
// _age++; // 错误:无法直接访问 private 成员
func
// 通过公有接口访问
protected
int
int main()
identity
// 公有成员可访问
func
// 公有接口访问私有数据
return
0
模板继承示例

通过继承类模板可以实现更灵活的容器封装,例如用 vector 实现栈。

namespace example {
    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() {
    example::stack<int> st;
    st.push(1);
    st.push(2);
    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

基类和派生类间的转换

2.1 类型转换与临时变量

不同类型间转换(如 int 转 double)会产生临时对象。但在基类和派生类之间,情况有所不同。

2.2 赋值兼容规则(切片)

派生类对象可以赋值给基类的指针或引用,这个过程不会产生临时变量,被称为'切片'或'切割'。

文章配图

class Person {
protected:
    string _name;
    int _age;
};

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

int main() {
    Student sobj;
    // 派生类对象赋值给基类指针/引用,无临时变量
    Person* pp = &sobj;
    Person& rp = sobj;
    // 派生类对象赋值给基类对象,调用拷贝构造,产生切片
    Person pobj = sobj;
    return 0;
}

规则总结:

  1. 向上转型:public 继承的派生类对象可赋值给基类指针/引用(天然支持)。
  2. 向下转型:基类对象不能直接赋值给派生类对象。基类指针/引用可通过强制转换指向派生类对象,但需确保实际指向的是派生类对象(多态场景下建议使用 dynamic_cast)。

继承中的作用域

继承体系中,基类和派生类拥有独立的作用域。同名成员可能导致隐藏。

3.1 隐藏规则

  1. 变量隐藏:派生类成员屏蔽基类同名成员的直接访问。需通过作用域解析运算符 Base::member 显式访问。
  2. 函数隐藏:只要函数名相同即构成隐藏,无需参数不同(这与重载不同,重载要求同一作用域且参数不同)。
class Person {
protected:
    string _name = "wdefg";
    int _num = 111;
};

class Student : public Person {
public:
    void Print() {
        cout << "姓名:" << _name << endl; // 访问派生类成员
        cout << "身份证号:" << Person::_num << endl; // 显式访问基类成员
    }
protected:
    int _num = 999; // 隐藏了基类的 _num
};

3.2 函数隐藏 vs 重载

基类和派生类之间的同名函数构成隐藏关系,而非重载。

class A {
public:
    void fun() { cout << "func()" << endl; }
};

class B : public A {
public:
    void fun(int i) { cout << "func(int i)" << endl; }
};

int main() {
    B b;
    b.fun(1);      // 调用 B::fun(int)
    b.A::fun();    // 必须显式指定才能调用 A::fun()
}

派生类的默认成员函数

派生类对象的生命周期涉及基类部分的初始化和清理。

核心规则:

  1. 构造顺序:先调用基类构造函数,再调用派生类构造函数。
  2. 析构顺序:先调用派生类析构函数,再调用基类析构函数。
  3. 拷贝构造与赋值:派生类需显式调用基类的对应函数。注意赋值运算符重载会隐藏基类的版本,需通过 Base::operator= 调用。
  4. 虚析构:若涉及多态,基类析构函数应声明为 virtual,否则派生类析构函数会与基类形成隐藏,导致资源泄漏。
class Person {
public:
    Person(const char* name = "peter") : _name(name) { cout << "Person()" << endl; }
    ~Person() { cout << "~Person()" << endl; }
protected:
    string _name;
};

class Student : public Person {
public:
    Student(const char* name) : Person(name) {} // 显式调用基类构造
    Student& operator=(const Student& s) {
        if (this != &s) {
            Person::operator=(s); // 显式调用基类赋值
            // ... 处理派生类成员
        }
        return *this;
    }
    ~Student() { /* 编译器自动调用基类析构 */ }
};

实现不能被继承的类

有两种常见方法:

  1. C++98:将基类构造函数设为 private,派生类无法调用。
  2. C++11:使用 final 关键字修饰类。
class Base final {
public:
    void func() { cout << "Base::func" << endl; }
};
// class Child : public Base {}; // 编译错误

继承与友元、静态成员

6.1 友元关系

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

6.2 静态成员

静态成员属于整个继承体系,所有类共享同一份实例,不会因继承产生副本。

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

class Student : public Person {
    // ...
};

int main() {
    Person p; Student s;
    cout << &p._count << endl; // 地址相同
    cout << &s._count << endl; // 地址相同
    return 0;
}

多继承、菱形继承与虚继承

7.1 继承模型

  • 单继承:一个派生类只有一个直接基类。
  • 多继承:一个派生类有多个直接基类。内存布局通常是基类依次排列,派生类成员在最后。
  • 菱形继承:多继承的特殊情况,导致数据冗余和二义性。

文章配图

7.2 菱形继承问题

当两个中间类都继承自同一个基类时,最底层的派生类会包含两份基类成员,造成二义性和冗余。

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 {
    // 此时 Assistant 中有两份 Person 的成员
};

7.3 解决方案

  1. 指定作用域:解决二义性,但无法解决数据冗余。
  2. 虚拟继承:使用 virtual 关键字,确保基类在内存中只存在一份。
class Student : virtual public Person { ... };
class Teacher : virtual public Person { ... };
// Assistant 中 Person 成员唯一

7.4 指针偏移

多继承中,基类指针与派生类指针的地址可能不同,取决于基类在内存中的位置。

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 != p2 != p3
}

继承与组合

  • 继承(is-a):派生类对象是基类对象的一种。破坏封装,耦合度高。
  • 组合(has-a):对象中包含另一个对象。黑箱复用,耦合度低。

原则:优先使用组合,除非明确需要 is-a 关系或多态需求。

class Tire { ... };
class Car {
    Tire _t1, _t2, _t3, _t4; // 组合
};

class BMW : public Car { ... }; // 继承

多继承虽强大,但底层复杂且易引发菱形问题,实际设计中应谨慎使用。

目录

  1. 继承的概念及定义
  2. 1.1 继承的定义格式
  3. 1.2 成员访问权限的变化
  4. 示例:继承与访问控制
  5. 模板继承示例
  6. 基类和派生类间的转换
  7. 2.1 类型转换与临时变量
  8. 2.2 赋值兼容规则(切片)
  9. 继承中的作用域
  10. 3.1 隐藏规则
  11. 3.2 函数隐藏 vs 重载
  12. 派生类的默认成员函数
  13. 实现不能被继承的类
  14. 继承与友元、静态成员
  15. 6.1 友元关系
  16. 6.2 静态成员
  17. 多继承、菱形继承与虚继承
  18. 7.1 继承模型
  19. 7.2 菱形继承问题
  20. 7.3 解决方案
  21. 7.4 指针偏移
  22. 继承与组合
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • OpenClaw 智能体框架环境搭建与模型接入指南
  • Music Tag Web Docker 部署指南
  • C++ STL list 容器详解:使用与模拟实现
  • C++ 入门:string 类详解与模拟实现
  • ERNIE-4.5-0.3B 超轻量模型部署与能力评测指南
  • Java ArrayList 底层原理与核心方法手写实现
  • Flutter for OpenHarmony 实战:Material Color Utilities 算法驱动动态换肤
  • 精选 GitHub 上 7 款热门 Claude Skills 开源工具
  • Python Windows 环境搭建与 PyCharm 配置实战
  • Python 函数、列表与元组核心用法及实战案例
  • SkyWalking 多语言探针现状:.NET / C++ / Lua 与社区支持
  • Mac M 芯片 OpenClaw 本地部署与配置指南
  • C++ 继承中同名成员的隐藏规则与重载辨析
  • 解决 WSL 中 VS Code Copilot 无法连接的网络代理问题
  • MySQL 表约束核心指南:从基础到外键实战
  • TRAE、Qoder、Cursor 与 GitHub Copilot 深度对比:AI 编程工具选型指南
  • 滑动窗口算法详解:解决子数组和子串问题
  • AgentScope Java 智能体框架入门与进阶
  • 自然语言处理在金融领域的应用与实战
  • 英伟达与 GitHub 免费大模型 API Key 获取实战

相关免费在线工具

  • 加密/解密文本

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