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

C++ 继承:语法、访问权限与虚继承详解

C++ 继承允许子类复用基类成员。涵盖继承语法、三种访问方式(public/protected/private)对成员权限的影响、内存大小计算规则、构造与析构顺序、同名成员隐藏处理、多继承及菱形继承问题。重点介绍了使用虚继承解决菱形继承中的二义性和数据冗余,并解释了虚基类表指针的工作原理。

禅心发布于 2026/3/27更新于 2026/10/2299 浏览

继承的语法

利用继承可以减少重复代码出现;

语法:class 子类 : 继承方式 父类 {};

// 基类(父类)
class Animal {
public:
    void eat() { cout << "Animal is eating" << endl; }
    void sleep() { cout << "Animal is sleeping" << endl; }
};

// 派生类(子类)
class Dog : public Animal {
    // Dog 继承自 Animal
public:
    void bark() { cout << "Dog is barking" << endl; }
};

int main() {
    Dog myDog;
    myDog.eat();      // 继承自 Animal
    myDog.sleep();    // 继承自 Animal
    myDog.bark();     // Dog 自己的方法
    return 0;
}

继承方式

不同的继承方式决定了基类成员在派生类中的访问权限;

  1. 公共继承(class 子类 : public 父类)
  2. 保护继承(class 子类 : protected 父类)
  3. 私有继承(class 子类 : private 父类)

三者区别:

基类成员权限public 继承protected 继承private 继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问

公共继承 (public)

特点: 保持基类的访问权限不变

class Base {
public:
    int publicData = 1;
protected:
    int protectedData = 2;
private:
    int privateData = 3;
};

class Derived : public Base {
public:
    void test() {
        publicData = 10;       // ✓ 仍然是 public
        protectedData = 20;    // ✓ 仍然是 protected
        // privateData = 30;   // ✗ 私有成员不可访问
    }
};

void testPublic() {
    Derived d;
    d.publicData = 100;      // ✓ 外部可以访问 public 成员
    // d.protectedData = 200; // ✗ 外部不能访问 protected 成员
}

保护继承 (protected)

特点: 基类的 public 和 protected 成员都变为 protected

class Base {
public:
    int publicData = 1;
protected:
    int protectedData = 2;
private:
    int privateData = 3;
};

class Derived : protected Base {
public:
    void test() {
        publicData = 10;       // ✓ 变为 protected,类内可访问
        protectedData = 20;    // ✓ 仍然是 protected
        // privateData = 30;   // ✗ 私有成员不可访问
    }
};

void testProtected() {
    Derived d;
    // d.publicData = 100;    // ✗ 外部不能访问,已变为 protected
}

class GrandChild : public Derived {
public:
    void test() {
        publicData = 100;      // ✓ 子类可以访问 protected 成员
        protectedData = 200;   // ✓ 子类可以访问 protected 成员
    }
};

私有继承 (private)

class Base {
public:
    int publicData = 1;
protected:
    int protectedData = 2;
private:
    int privateData = 3;
};

class Derived : private Base {
public:
    void test() {
        publicData = 10;       // ✓ 变为 private,类内可访问
        protectedData = 20;    // ✓ 变为 private,类内可访问
        // privateData = 30;   // ✗ 私有成员不可访问
    }
};

void testPrivate() {
    Derived d;
    // d.publicData = 100;    // ✗ 外部不能访问,已变为 private
}

class GrandChild : public Derived {
public:
    void test() {
        // publicData = 100;   // ✗ 孙类不能访问,已变为 private
        // protectedData = 200; // ✗ 孙类不能访问,已变为 private
    }
};

继承之后子类的大小

继承只影响访问权限,不影响数据大小;private 成员虽然不能访问,但仍被继承并占用内存。

子类大小 = 基类大小 + 派生类新增成员大小 + 对齐填充;与继承方式无关。

举个栗子:

class Base {
public:
    int m_A; // 4 字节
protected:
    int m_B; // 4 字节
private:
    int m_C; // 4 字节
};

class PublicSon : public Base {
public:
    int m_D; // 4 字节
};

class ProtectedSon : protected Base {
public:
    int m_D; // 4 字节
};

class PrivateSon : private Base {
public:
    int m_D; // 4 字节
};

void testSize() {
    cout << "Base 大小:" << sizeof(Base) << endl;      // 12 字节
    cout << "PublicSon 大小:" << sizeof(PublicSon) << endl; // 16 字节
    cout << "ProtectedSon 大小:" << sizeof(ProtectedSon) << endl; // 16 字节
    cout << "PrivateSon 大小:" << sizeof(PrivateSon) << endl;   // 16 字节
}

上述代码的内存分布:

// 内存布局(所有继承方式都一样)
class AnySon : [any_inheritance] Base {
public:
    int m_D;
};
// 内存结构:
// [Base 部分:m_A(4) + m_B(4) + m_C(4)] + [Son 部分:m_D(4)] = 16 字节
// ↑                    ↑
// 基类成员             派生类成员

继承中的析构和构造

  • 构造顺序:(基类) ➡ 成员对象 ➡ 子类 (派生类)
  • 析构顺序:子类 (派生类) ➡ 成员对象 ➡ 父类 (基类)

没有成员对象构造、析构顺序:

class Base1 {
public:
    Base1() { cout << "Base1 构造" << endl; }
    ~Base1() { cout << "Base1 析构" << endl; }
};

class Base2 {
public:
    Base2() { cout << "Base2 构造" << endl; }
    ~Base2() { cout << "Base2 析构" << endl; }
};

class Derived : public Base1, public Base2 {
    // 继承顺序决定构造顺序
public:
    Derived() { cout << "Derived 构造" << endl; }
    ~Derived() { cout << "Derived 析构" << endl; }
};

void test() {
    Derived d;
    // 输出:
    // Base1 构造 ← 按继承列表顺序
    // Base2 构造
    // Derived 构造
    // Derived 析构 ← 析构顺序相反
    // Base2 析构
    // Base1 析构
}

有成员对象构造、析构顺序:

class Component1 {
public:
    Component1() { cout << "Component1 构造" << endl; }
    ~Component1() { cout << "Component1 析构" << endl; }
};

class Component2 {
public:
    Component2() { cout << "Component2 构造" << endl; }
    ~Component2() { cout << "Component2 析构" << endl; }
};

class Base {
public:
    Base() { cout << "Base 构造" << endl; }
    ~Base() { cout << "Base 析构" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived 构造" << endl; }
    ~Derived() { cout << "Derived 析构" << endl; }
private:
    Component1 comp1; // 成员对象按声明顺序构造
    Component2 comp2;
};

void test() {
    Derived d;
    // 输出:
    // Base 构造 ← 1. 基类构造
    // Component1 构造 ← 2. 成员对象按声明顺序构造
    // Component2 构造
    // Derived 构造 ← 3. 派生类构造函数体
    // Derived 析构 ← 析构顺序完全相反
    // Component2 析构
    // Component1 析构
    // Base 析构
}

继承中的同名成员的处理

  • 派生类的同名成员会隐藏基类的所有同名成员(包括重载版本)
  • 作用域解析: 使用 :: 明确指定调用哪个函数;
  • 静态成员与普通成员相同:派生类的同名静态成员会隐藏基类的所有同名静态成员
class Base {
public:
    Base(){ this->m_A = 100; }
    void func(){ cout << "Base 中的 Func 调用" << endl; }
    void func(int a){ cout << "Base 中的 Func(int a) 调用" << endl; }
    int m_A;
};

class Son : public Base {
public:
    Son(){ this->m_A = 200; }
    void func(){ cout << "Son 中的 Func 调用" << endl; }
    int m_A;
};

void test01() {
    Son s;
    cout << s.m_A << endl;                  // 调用 Son 类的 m_A
    cout << "Base 中的 m_A " << s.Base::m_A << endl; // 调用 Base 类中的 m_A
    // 同名的成员函数,子类会隐藏掉父类中的所有版本
    s.func();                               // 调用 Son 类的 func
    s.Base::func(10);                       // 调用 Base 类的 func
}

int main(){
    test01();
    system("pause");
    return EXIT_SUCCESS;
}

多继承

多继承允许一个类同时从多个基类继承,获得多个基类的特性。

语法:class 子类 : 继承方式 父类 1,继承方式 父类 2,... {};

class Base1 {
public:
    int data1 = 10;
    void func1() { cout << "Base1::func1()" << endl; }
};

class Base2 {
public:
    int data2 = 20;
    void func2() { cout << "Base2::func2()" << endl; }
};

// 多继承语法:用逗号分隔多个基类
class Derived : public Base1, public Base2 {
public:
    int data3 = 30;
    void showAll() {
        cout << "data1: " << data1 << endl; // 来自 Base1
        cout << "data2: " << data2 << endl; // 来自 Base2
        cout << "data3: " << data3 << endl; // 自己的成员
    }
};

void testBasic() {
    Derived d;
    d.func1(); // 调用 Base1 的方法
    d.func2(); // 调用 Base2 的方法
    d.showAll(); // 调用自己的方法
}

多继承的构造和析构

  • 构造顺序: 按继承列表中的顺序依次调用基类构造函数,最后调用派生类构造函数
  • 析构顺序: 与构造顺序完全相反
class Base1 {
public:
    Base1() { cout << "Base1 构造函数调用" << endl; }
    ~Base1() { cout << "Base1 析构函数调用" << endl; }
};

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

class Derived : public Base1, public Base2 {
    // 继承顺序:Base1, Base2
public:
    Derived() { cout << "Derived 构造函数调用" << endl; }
    ~Derived() { cout << "Derived 析构函数调用" << endl; }
};

void testOrder() {
    cout << "=== 创建对象 ===" << endl;
    Derived d;
    cout << "=== 对象销毁 ===" << endl;
}
// 输出:
// === 创建对象 ===
// Base1 构造函数调用 ← 按继承列表顺序
// Base2 构造函数调用
// Derived 构造函数调用
// === 对象销毁 ===
// Derived 析构函数调用 ← 析构顺序相反
// Base2 析构函数调用
// Base1 析构函数调用

菱形继承的问题和解决方法

菱形继承的问题

  • 什么是菱形继承:
    • 菱形继承是指一个派生类通过多个路径继承同一个基类,形成菱形的继承结构。
    • 通俗的说:在这种继承结构中,羊和驼都继承自动物,而 羊驼又从羊和 驼继承。这就可能导致羊驼继承动物的两个实例,我们喊一声动物不知道喊的是羊还是驼,造成二义性。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

// 动物类
class Animal {
public:
    int age = 0;
    string name = "动物";
};

class Sheep : public Animal {
public:
    void bleat() { cout << name << "咩咩叫" << endl; }
};

class Tuo : public Animal {
public:
    void spit() { cout << name << "吐口水" << endl; }
};

class SheepTuo : public Sheep, public Tuo {
public:
    void showInfo() {
        // cout << age; // ✗ 错误!二义性
        // 问题:SheepTuo 对象包含两份 Animal 数据
        cout << "Sheep 的 age: " << Sheep::age << endl;
        cout << "Tuo 的 age: " << Tuo::age << endl;
    }
};

void testProblem() {
    SheepTuo st;
    st.Sheep::age = 5;
    st.Tuo::age = 8;
    cout << "对象大小:" << sizeof(st) << endl; // 包含两份 Animal 数据
    // 必须明确指定路径
    cout << "通过 Sheep 访问:" << st.Sheep::age << endl; // 5
    cout << "通过 Tuo 访问:" << st.Tuo::age << endl;   // 8
}

/***********************************************************************************/
// 普通多继承的内存布局
class SheepTuo : public Sheep, public Tuo {
    // 内存布局:
    // [Sheep 部分:Animal 数据] + [Tuo 部分:Animal 数据] + [SheepTuo 数据]
    // ↑                    ↑
    // 重复的 Animal 数据     重复的 Animal 数据
};

使用虚继承解决菱形继承问题 (virtual)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

// 动物类
class Animal {
public:
    int m_Age; // 年龄
};

// virtual 加上后 继承方式 数据虚继承
// Animal 类 变为 虚基类
// 羊类
class Sheep : virtual public Animal{};
// 驼
class Tuo : virtual public Animal {};
// 羊驼
class SheepTuo : public Sheep, public Tuo{};

void test01() {
    SheepTuo st;
    st.Sheep::m_Age = 10;
    st.Tuo::m_Age = 20;
    cout << "age = " << st.Sheep::m_Age << endl;
    cout << "age = " << st.Tuo::m_Age << endl;
    cout << "age = " << st.m_Age << endl; // m_Age 只需要一份即可,菱形继承导致数据有一份浪费
}

// 虚继承内部工作原理
void test02() {
    SheepTuo st;
    st.m_Age = 100;
    // 通过 sheep 找到偏移量值
    cout << "通过 sheep 找到的偏移量为:" << *(int *)((int*)*(int *)&st + 1) << endl;
    // 通过 Tuo 找到偏移量值
    cout << "通过 Tuo 找到的偏移量为:" << *(int *)((int *)*((int *)&st + 1) + 1) << endl;
    // 通过偏移量 求出 m_Age 的值
    cout << "age = " << ((Animal *)((char *)&st + (*(int *)((int*)*(int *)&st + 1))))->m_Age << endl;
}

int main(){
    //test01();
    test02();
    system("pause");
    return EXIT_SUCCESS;
}

当我们访问无论是访问 sheep 中的 m_Age 还是 Tuo 中的 m_Age,实际上都是访问的 Animal 的 m_Age; 如果在 sheep 中定义一个同名的 m_Age,会将 Animal 中的 m_Age 隐藏,但是还是会继承;

使用虚函数之后的内存布局:

  1. 虚基类表指针 - 每个虚继承的类都有一个 vbptr
  2. 偏移量存储 - 虚基类表存储从派生类到虚基类的偏移量
  3. 间接访问 - 访问虚基类成员需要通过虚基类表进行间接寻址
  4. 性能开销 - 额外的指针和间接访问增加了开销
  5. 编译器优化 - 编译器自动生成虚基类表和访问代码

虚继承的实现原理

每个虚继承的类都有一个虚基类表,存储在只读数据区 每个继承虚继承的类都有一个虚基类表指针 vbptr,指向一个虚基类表

继承虚表中成员:

  • offset_to_top:从当前虚基类表指针(vbptr)所在位置到完整派生类对象顶部的偏移量。
  • offset_to_virtual_base:到虚基类的偏移
class Animal {
public:
    int m_Age;
};

class Sheep : virtual public Animal {
    // 内存布局:
    // [vbptr] → 虚基类表指针
    // [Animal 数据] → 虚基类数据
};

class Tuo : virtual public Animal {
    // 内存布局:
    // [vbptr] → 虚基类表指针
    // [Animal 数据] → 虚基类数据
};

class SheepTuo : public Sheep, public Tuo {
    // 内存布局:
    // [Sheep 部分:vbptr_Sheep] → 指向 Sheep 的虚基类表
    // [Tuo 部分:vbptr_Tuo] → 指向 Tuo 的虚基类表
    // [Animal 数据:m_Age] → 共享的虚基类数据
};

访问过程:

  1. 虚基类表指针 - 每个虚继承的类都有一个 vbptr
  2. 偏移量存储 - 虚基类表存储从派生类到虚基类的偏移量
  3. 间接访问 - 访问虚基类成员需要通过虚基类表进行间接寻址
  4. 性能开销 - 额外的指针和间接访问增加了开销
  5. 编译器优化 - 编译器自动生成虚基类表和访问代码

目录

  1. 继承的语法
  2. 继承方式
  3. 公共继承 (public)
  4. 保护继承 (protected)
  5. 私有继承 (private)
  6. 继承之后子类的大小
  7. 继承中的析构和构造
  8. 继承中的同名成员的处理
  9. 多继承
  10. 多继承的构造和析构
  11. 菱形继承的问题和解决方法
  12. 菱形继承的问题
  13. 使用虚继承解决菱形继承问题 (virtual)
  14. 虚继承的实现原理

更多推荐文章

查看全部
  • 基于 Java、GeoTools 与 PostGIS 的对跖点求解
  • C++ 函数重载:核心规则、实现细节与实战
  • 本地部署 GraphRAG 教程及与传统 RAG 效果对比
  • RAG、知识库与大语言模型(LLM)技术详解
  • Pi0 机器人 VLA 大模型在昇腾 A2 平台上的测评
  • 数字化转型的本质:从信息化到数字化的跨越
  • B-树模拟实现详解
  • C++ 基础笔记:命名空间、IO、缺省参数及 Makefile
  • 23 个热门开源智能体框架深度解析:从 LangChain 到 ElizaOS
  • 基于 Rust 与大模型的智能 API Mock 生成器构建
  • S1: 仅 1k 样本,测试时扩展超越 OpenAI o1-preview 性能
  • SkyWalking 与 Spring Cloud Alibaba 全链路追踪实战
  • Linux 基础 IO 收官:库的构建与使用、进程地址空间及核心知识点
  • Visual C++ 运行库安装故障排查与修复指南
  • Java 数据结构:链表原理与 LinkedList 核心应用
  • 从零构建可扩展 Flutter 应用:v1.0 到 v2.0 架构演进详解
  • Java 环境配置与第一个 Hello World 程序实战
  • GitHub Copilot Plan 模式核心价值与适用场景
  • 基于 LangChain 搭建本地知识库系统
  • 无人机三维路径规划:蚁群、A* 与 RRT* 算法对比及 Matlab 实现

相关免费在线工具

  • 加密/解密文本

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