引言
继承是面向对象编程的核心特性之一,但很多初学者对继承的理解仅仅停留在"子类拥有父类的成员"这个层面。然而,在实际开发中,我们需要深入理解:派生类对象在内存中是如何布局的?基类对象和成员对象有什么区别?多重继承会带来什么问题?
今天,我将通过详细的内存模型分析和代码示例,系统地讲解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++ 面向对象编程的核心,理解其底层机制对于写出正确、高效的代码至关重要。从存储布局到构造析构顺序,从赋值兼容到多重继承,每个知识点都值得深入理解。
学习建议:
-
理解内存布局:知道对象在内存中如何组织
-
掌握构造/析构顺序:避免资源管理错误
-
谨慎使用多重继承:优先考虑组合
-
善用虚继承:解决菱形继承问题