引言
继承是面向对象编程的核心特性之一,但很多初学者对继承的理解仅仅停留在“子类拥有父类的成员”这个层面。然而,在实际开发中,我们需要深入理解:派生类对象在内存中是如何布局的?基类对象和成员对象有什么区别?多重继承会带来什么问题?
今天,我将通过详细的内存模型分析和代码示例,系统地讲解C++继承中的高级特性。
第一部分:继承关系中对象的存储布局
一、单继承的内存布局
#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; }二、对象存储布局图解
三、基类对象与成员对象的区别
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 */关键区别:
| 类型 | 构造时机 | 析构时机 | 内存位置 |
|---|---|---|---|
| 基类子对象 | 最先构造 | 最后析构 | 派生类对象内部 |
| 成员对象 | 按声明顺序构造 | 按相反顺序析构 | 派生类对象内部 |
| 局部对象 | 执行到定义处 | 离开作用域 | 栈上 |
第二部分:同名隐藏与作用域
一、同名隐藏规则
当派生类定义了与基类同名的成员时,基类的成员会被隐藏。
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; |
class Derived : public Base { public: using Base::func; // 将基类的所有 func 引入当前作用域 void func() { cout << "Derived::func()" << endl; } }; // 现在 d.func(10) 可以正常调用第三部分:赋值兼容规则
一、基本规则
派生类对象可以赋值给基类对象,但反过来不行。
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; }二、切片问题
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; }第四部分:继承中的构造与析构
一、构造和析构顺序
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析构 */构造顺序:基类 → 成员对象 → 派生类构造函数体
析构顺序:完全相反
二、基类没有无参构造的情况
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) { } };第五部分:派生类的拷贝构造函数与赋值运算符
一、拷贝构造函数
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; }二、拷贝赋值运算符
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; }第六部分:公有继承与私有继承的区别
一、访问权限变化
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" | 实现继承,不表达接口 |
// 公有继承:表达 is-a 关系 class Dog : public Animal { }; // Dog 是一种 Animal // 私有继承:实现复用,不表达接口 class Timer : private Clock { // Timer 使用 Clock 的功能,但不是一种 Clock };第七部分:继承与静态成员
静态成员在整个继承体系中只有一份,所有派生类共享。
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; }第八部分:继承与友元
友元关系不能继承。基类的友元不会自动成为派生类的友元。
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; }第九部分:多重继承
一、基本语法
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; }二、菱形继承问题
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; }三、虚继承解决菱形继承
// 虚继承:共享同一份基类子对象 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; }四、虚继承的内存布局
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 的虚基类指针第十部分:完整示例
#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 关系,最常用 |
| 私有继承 | 实现继承,不表达接口 |
| 虚继承 | 解决菱形继承问题 |
二、三/五法则扩展
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++ 面向对象编程的核心,理解其底层机制对于写出正确、高效的代码至关重要。从存储布局到构造析构顺序,从赋值兼容到多重继承,每个知识点都值得深入理解。
学习建议:
理解内存布局:知道对象在内存中如何组织
掌握构造/析构顺序:避免资源管理错误
谨慎使用多重继承:优先考虑组合
善用虚继承:解决菱形继承问题