C++ 继承与派生深度解析:存储布局、构造析构与高级特性

引言

继承是面向对象编程的核心特性之一,但很多初学者对继承的理解仅仅停留在"子类拥有父类的成员"这个层面。然而,在实际开发中,我们需要深入理解:派生类对象在内存中是如何布局的?基类对象和成员对象有什么区别?多重继承会带来什么问题?

今天,我将通过详细的内存模型分析和代码示例,系统地讲解C++继承中的高级特性。

第一部分:继承关系中对象的存储布局

一、单继承的内存布局

cpp 复制代码
#include <iostream>
using namespace std;

class Base {
private:
    int b1;
    int b2;
public:
    Base() : b1(10), b2(20) {}
    virtual void func() { cout << "Base::func" << endl; }
};

class Derived : public Base {
private:
    int d1;
    int d2;
public:
    Derived() : d1(100), d2(200) {}
    virtual void func() override { cout << "Derived::func" << endl; }
};

int main() {
    Derived d;
    // 内存布局(32位系统):
    // [vptr][b1][b2][d1][d2]
    //  4B   4B  4B  4B  4B = 20B
    
    cout << "Derived size: " << sizeof(Derived) << endl;
    // 输出:20(取决于虚表指针和对齐)
    
    return 0;
}

二、对象存储布局图解

三、基类对象与成员对象的区别

cpp 复制代码
class Member {
private:
    int m;
public:
    Member(int val) : m(val) { cout << "Member构造: " << m << endl; }
    ~Member() { cout << "Member析构: " << m << endl; }
};

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

class Derived : public Base {      // 基类子对象
private:
    Member m1;                      // 成员对象
    Member m2;                      // 成员对象
public:
    Derived(int b, int m1v, int m2v) 
        : Base(b), m1(m1v), m2(m2v) {
        cout << "Derived构造" << endl;
    }
    ~Derived() { cout << "Derived析构" << endl; }
};

int main() {
    Derived d(10, 20, 30);
    return 0;
}

/* 输出顺序(构造):
Base构造: 10        ← 1. 基类子对象
Member构造: 20      ← 2. 成员对象(按声明顺序)
Member构造: 30      ← 3. 成员对象
Derived构造         ← 4. 派生类构造函数体

输出顺序(析构,完全相反):
Derived析构
Member析构: 30
Member析构: 20
Base析构: 10
*/

关键区别:

类型 构造时机 析构时机 内存位置
基类子对象 最先构造 最后析构 派生类对象内部
成员对象 按声明顺序构造 按相反顺序析构 派生类对象内部
局部对象 执行到定义处 离开作用域 栈上

第二部分:同名隐藏与作用域

一、同名隐藏规则

当派生类定义了与基类同名的成员时,基类的成员会被隐藏

cpp 复制代码
class Base {
public:
    int value = 10;
    void func() { cout << "Base::func()" << endl; }
    void func(int x) { cout << "Base::func(int): " << x << endl; }
};

class Derived : public Base {
public:
    int value = 100;  // 隐藏基类的 value
    void func() { cout << "Derived::func()" << endl; }  // 隐藏所有基类 func
};

int main() {
    Derived d;
    
    cout << d.value << endl;      // 100(派生类成员)
    // cout << d.Base::value << endl;  // 10(通过作用域访问)
    
    d.func();                      // Derived::func()
    // d.func(10);                 // 错误!func(int) 被隐藏了
    d.Base::func(10);              // 正确:显式调用基类版本
    
    return 0;
}

二、重载与隐藏的区别

场景 行为 说明
基类重载函数 派生类可继承 所有重载版本都可用
派生类定义同名函数 隐藏所有基类版本 无论参数是否相同
使用 using 声明 可引入基类重载 using Base::func;
cpp 复制代码
class Derived : public Base {
public:
    using Base::func;  // 将基类的所有 func 引入当前作用域
    void func() { cout << "Derived::func()" << endl; }
};
// 现在 d.func(10) 可以正常调用

第三部分:赋值兼容规则

一、基本规则

派生类对象可以赋值给基类对象,但反过来不行。

cpp 复制代码
class Base {
public:
    int b;
    Base(int val = 0) : b(val) {}
};

class Derived : public Base {
public:
    int d;
    Derived(int bv = 0, int dv = 0) : Base(bv), d(dv) {}
};

int main() {
    Derived d(10, 20);
    Base b;
    
    // 1. 派生类对象赋值给基类对象(切片)
    b = d;           // 只复制基类部分,d.d 丢失
    
    // 2. 派生类指针/引用赋值给基类指针/引用
    Base* pb = &d;   // 合法,指向派生类对象的基类部分
    Base& rb = d;    // 合法
    
    // 3. 反过来不行
    // Derived* pd = &b;  // 错误!基类不能赋值给派生类
    // d = b;              // 错误!
    
    return 0;
}

二、切片问题

cpp 复制代码
void printBase(Base b) {
    cout << "Base value: " << b.b << endl;
}

void printBaseRef(Base& b) {
    cout << "Base value: " << b.b << endl;
}

int main() {
    Derived d(10, 20);
    
    printBase(d);      // 传值:发生切片,丢失派生类部分
    printBaseRef(d);   // 传引用:不发生切片,保留派生类信息
    
    return 0;
}

第四部分:继承中的构造与析构

一、构造和析构顺序

cpp 复制代码
class Grand {
public:
    Grand() { cout << "Grand构造" << endl; }
    ~Grand() { cout << "Grand析构" << endl; }
};

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

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

int main() {
    Child c;
    return 0;
}

/* 输出:
Grand构造
Parent构造
Child构造
Child析构
Parent析构
Grand析构
*/

构造顺序: 基类 → 成员对象 → 派生类构造函数体
析构顺序: 完全相反

二、基类没有无参构造的情况

cpp 复制代码
class Base {
private:
    int b;
public:
    Base(int val) : b(val) {}  // 没有无参构造
};

class Derived : public Base {
public:
    // 错误!基类没有无参构造
    // Derived() { }  // 编译错误
    
    // 正确:在初始化列表中显式调用基类构造
    Derived(int val) : Base(val) { }
    
    // 委托给其他构造函数
    Derived() : Derived(0) { }
};

第五部分:派生类的拷贝构造函数与赋值运算符

一、拷贝构造函数

cpp 复制代码
class Base {
protected:
    int b;
public:
    Base(int val = 0) : b(val) {}
    Base(const Base& other) : b(other.b) {
        cout << "Base拷贝构造" << endl;
    }
};

class Derived : public Base {
private:
    int d;
public:
    Derived(int bv = 0, int dv = 0) : Base(bv), d(dv) {}
    
    // 派生类拷贝构造函数
    Derived(const Derived& other) 
        : Base(other),   // 调用基类拷贝构造
          d(other.d) {   // 拷贝派生类成员
        cout << "Derived拷贝构造" << endl;
    }
    
    void show() const {
        cout << "b=" << b << ", d=" << d << endl;
    }
};

int main() {
    Derived d1(10, 20);
    Derived d2(d1);  // 调用拷贝构造
    d2.show();       // b=10, d=20
    return 0;
}

二、拷贝赋值运算符

cpp 复制代码
class Base {
protected:
    int b;
public:
    Base(int val = 0) : b(val) {}
    Base& operator=(const Base& other) {
        if (this != &other) {
            b = other.b;
            cout << "Base赋值运算符" << endl;
        }
        return *this;
    }
};

class Derived : public Base {
private:
    int d;
public:
    Derived(int bv = 0, int dv = 0) : Base(bv), d(dv) {}
    
    // 派生类赋值运算符
    Derived& operator=(const Derived& other) {
        if (this != &other) {
            Base::operator=(other);  // 调用基类赋值运算符
            d = other.d;              // 赋值派生类成员
            cout << "Derived赋值运算符" << endl;
        }
        return *this;
    }
    
    void show() const {
        cout << "b=" << b << ", d=" << d << endl;
    }
};

int main() {
    Derived d1(10, 20);
    Derived d2;
    d2 = d1;  // 调用赋值运算符
    d2.show(); // b=10, d=20
    return 0;
}

第六部分:公有继承与私有继承的区别

一、访问权限变化

cpp 复制代码
class Base {
private:
    int a = 1;
protected:
    int b = 2;
public:
    int c = 3;
};

// 公有继承:基类成员保持原有访问权限
class PublicDerived : public Base {
    // a: 不可访问
    // b: protected
    // c: public
};

// 私有继承:基类成员全部变为 private
class PrivateDerived : private Base {
    // a: 不可访问
    // b: private
    // c: private
};

int main() {
    PublicDerived pub;
    // pub.b;  // 错误!protected 不可访问
    pub.c;     // 正确!public 可访问
    
    PrivateDerived pri;
    // pri.c;  // 错误!私有继承后变为 private
    
    return 0;
}

二、使用场景

继承方式 语义 使用场景
public "is-a" 关系 绝大多数情况
protected 少见 实现复用
private "implemented-in-terms-of" 实现继承,不表达接口
cpp 复制代码
// 公有继承:表达 is-a 关系
class Dog : public Animal { };  // Dog 是一种 Animal

// 私有继承:实现复用,不表达接口
class Timer : private Clock {
    // Timer 使用 Clock 的功能,但不是一种 Clock
};

第七部分:继承与静态成员

静态成员在整个继承体系中只有一份,所有派生类共享。

cpp 复制代码
class Base {
public:
    static int count;
    Base() { count++; }
    ~Base() { count--; }
};
int Base::count = 0;

class Derived : public Base { };

int main() {
    cout << Base::count << endl;    // 0
    Base b1, b2;
    cout << Base::count << endl;    // 2
    Derived d1, d2;
    cout << Base::count << endl;    // 4(所有对象共享)
    cout << Derived::count << endl; // 4(通过派生类访问)
    
    return 0;
}

第八部分:继承与友元

友元关系不能继承。 基类的友元不会自动成为派生类的友元。

cpp 复制代码
class Base {
private:
    int secret = 10;
    friend void friendOfBase(Base& b);
};

class Derived : public Base {
private:
    int mySecret = 20;
};

void friendOfBase(Base& b) {
    cout << b.secret << endl;  // ✅ 可以访问 Base 的私有成员
}

void test() {
    Derived d;
    // friendOfBase(d);  // ✅ 可以传入派生类对象(访问 Base 部分)
    // 但无法访问 d.mySecret
}

int main() {
    Derived d;
    // cout << d.secret;  // 错误!secret 是 Base 的私有成员
    
    return 0;
}

第九部分:多重继承

一、基本语法

cpp 复制代码
class Father {
protected:
    int money = 1000;
public:
    void drive() { cout << "Father driving" << endl; }
};

class Mother {
protected:
    int money = 2000;
public:
    void cook() { cout << "Mother cooking" << endl; }
};

class Child : public Father, public Mother {
public:
    void show() {
        // cout << money;  // 二义性错误!不知道是 Father::money 还是 Mother::money
        cout << Father::money << endl;  // 1000
        cout << Mother::money << endl;  // 2000
    }
};

int main() {
    Child c;
    c.drive();  // Father 的成员
    c.cook();   // Mother 的成员
    c.show();
    return 0;
}

二、菱形继承问题

cpp 复制代码
class Father {
protected:
    int money = 1000;
public:
    void drive() { cout << "Father driving" << endl; }
};

class Mother {
protected:
    int money = 2000;
public:
    void cook() { cout << "Mother cooking" << endl; }
};

class Child : public Father, public Mother {
public:
    void show() {
        // cout << money;  // 二义性错误!不知道是 Father::money 还是 Mother::money
        cout << Father::money << endl;  // 1000
        cout << Mother::money << endl;  // 2000
    }
};

int main() {
    Child c;
    c.drive();  // Father 的成员
    c.cook();   // Mother 的成员
    c.show();
    return 0;
}

三、虚继承解决菱形继承

cpp 复制代码
// 虚继承:共享同一份基类子对象
class Grand {
protected:
    int value = 10;
};

class Parent1 : virtual public Grand { };
class Parent2 : virtual public Grand { };

class Child : public Parent1, public Parent2 {
public:
    void show() {
        cout << value << endl;  // ✅ 只有一个 Grand,无二义性
    }
};

int main() {
    Child c;
    c.show();  // 10
    return 0;
}

四、虚继承的内存布局

cpp 复制代码
class Grand {
    int g;
};
class Parent1 : virtual public Grand {
    int p1;
};
class Parent2 : virtual public Grand {
    int p2;
};
class Child : public Parent1, public Parent2 {
    int c;
};

// 虚继承内存布局(简化):
// [Parent1部分][Parent2部分][Grand部分][Child部分]
// Parent1 中有指向 Grand 的虚基类指针
// Parent2 中也有指向同一份 Grand 的虚基类指针

第十部分:完整示例

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    int id;
    string name;
protected:
    void setId(int i) { id = i; }
    void setName(string n) { name = n; }
public:
    Person(int i = 0, string n = "") : id(i), name(n) {
        cout << "Person构造: " << name << endl;
    }
    Person(const Person& other) : id(other.id), name(other.name) {
        cout << "Person拷贝构造: " << name << endl;
    }
    Person& operator=(const Person& other) {
        if (this != &other) {
            id = other.id;
            name = other.name;
            cout << "Person赋值运算符" << endl;
        }
        return *this;
    }
    virtual void show() const {
        cout << "Person: " << name << "(" << id << ")" << endl;
    }
    virtual ~Person() {
        cout << "Person析构: " << name << endl;
    }
};

class Student : virtual public Person {
private:
    float score;
public:
    Student(int i = 0, string n = "", float s = 0) 
        : Person(i, n), score(s) {
        cout << "Student构造: score=" << score << endl;
    }
    Student(const Student& other) 
        : Person(other), score(other.score) {
        cout << "Student拷贝构造" << endl;
    }
    Student& operator=(const Student& other) {
        if (this != &other) {
            Person::operator=(other);
            score = other.score;
            cout << "Student赋值运算符" << endl;
        }
        return *this;
    }
    void show() const override {
        Person::show();
        cout << "  Score: " << score << endl;
    }
    ~Student() {
        cout << "Student析构" << endl;
    }
};

class Teacher : virtual public Person {
private:
    string title;
public:
    Teacher(int i = 0, string n = "", string t = "") 
        : Person(i, n), title(t) {
        cout << "Teacher构造: title=" << title << endl;
    }
    void show() const override {
        Person::show();
        cout << "  Title: " << title << endl;
    }
    ~Teacher() {
        cout << "Teacher析构" << endl;
    }
};

// 多重继承 + 虚继承
class TeachingAssistant : public Student, public Teacher {
private:
    int workload;
public:
    TeachingAssistant(int i, string n, float s, string t, int w)
        : Person(i, n), Student(i, n, s), Teacher(i, n, t), workload(w) {
        cout << "TA构造: workload=" << workload << endl;
    }
    void show() const override {
        Person::show();
        cout << "  Score: " << score << endl;
        cout << "  Title: " << title << endl;
        cout << "  Workload: " << workload << "h" << endl;
    }
    ~TeachingAssistant() {
        cout << "TA析构" << endl;
    }
};

int main() {
    cout << "=== 创建 TA ===" << endl;
    TeachingAssistant ta(1001, "张三", 95.5, "助教", 20);
    
    cout << "\n=== 调用 show ===" << endl;
    ta.show();
    
    cout << "\n=== 拷贝构造 ===" << endl;
    TeachingAssistant ta2 = ta;
    
    cout << "\n=== 赋值运算 ===" << endl;
    TeachingAssistant ta3;
    ta3 = ta;
    
    cout << "\n=== 多态调用 ===" << endl;
    Person* p = &ta;
    p->show();
    
    cout << "\n=== 对象销毁 ===" << endl;
    return 0;
}

总结

一、继承关系核心要点

特性 说明
存储布局 基类子对象 + 派生类成员
构造顺序 基类 → 成员对象 → 派生类构造函数体
析构顺序 完全相反
同名隐藏 派生类同名成员隐藏基类所有重载
赋值兼容 派生类可赋值给基类(切片)
公有继承 is-a 关系,最常用
私有继承 实现继承,不表达接口
虚继承 解决菱形继承问题

二、三/五法则扩展

cpp 复制代码
class Derived : public Base {
public:
    // 构造函数
    Derived(...) : Base(...) { }
    
    // 析构函数
    ~Derived() { }
    
    // 拷贝构造
    Derived(const Derived& other) : Base(other) { }
    
    // 拷贝赋值
    Derived& operator=(const Derived& other) {
        if (this != &other) {
            Base::operator=(other);
            // 赋值派生类成员
        }
        return *this;
    }
    
    // 移动构造
    Derived(Derived&& other) : Base(move(other)) { }
    
    // 移动赋值
    Derived& operator=(Derived&& other) {
        if (this != &other) {
            Base::operator=(move(other));
            // 移动派生类成员
        }
        return *this;
    }
};

三、快速参考

场景 推荐做法
表达 is-a 关系 公有继承
实现复用 私有继承 或 组合
菱形继承 虚继承
基类无无参构造 初始化列表显式调用
避免切片 传引用或指针
多态 基类析构函数设为 virtual

继承是 C++ 面向对象编程的核心,理解其底层机制对于写出正确、高效的代码至关重要。从存储布局到构造析构顺序,从赋值兼容到多重继承,每个知识点都值得深入理解。

学习建议:

  1. 理解内存布局:知道对象在内存中如何组织

  2. 掌握构造/析构顺序:避免资源管理错误

  3. 谨慎使用多重继承:优先考虑组合

  4. 善用虚继承:解决菱形继承问题

相关推荐
我不是懒洋洋2 小时前
【经典题目】栈和队列面试题(括号匹配问题、用队列实现栈、设计循环队列、用栈实现队列)
c语言·开发语言·数据结构·算法·leetcode·链表·ecmascript
枫叶丹42 小时前
【HarmonyOS 6.0】ArkWeb PDF浏览能力增强:指定PDF文档背景色功能详解
开发语言·华为·pdf·harmonyos
谭欣辰2 小时前
C++ 控制台跑酷小游戏2.0
开发语言·c++·游戏程序
Huangxy__2 小时前
java相机手搓(后续是文件保存以及接入大模型)
java·开发语言·数码相机
刚子编程2 小时前
C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生
开发语言·c#·事务处理·trycatch
lsx2024062 小时前
jEasyUI 自定义对话框
开发语言
陶然同学2 小时前
【Python】文件操作
开发语言·python
Wild_Pointer.2 小时前
C++:内存顺序(Memory Order)的概念以及使用
c++
来自远方的老作者2 小时前
第10章 面向对象-10.3 封装
开发语言·python·私有属性·私有方法·封装