⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///
WARNING \]: DETECTING HIGH ENERGY
**🌊 🌉 🌊 心手合一 · 水到渠成**

|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| **\>\>\> ACCESS TERMINAL \<\<\<** ||
| [**\[ 🦾 作者主页 \]**](https://blog.csdn.net/fengtinghuqu520?spm=1000.2115.3001.5343) | [**\[ 🔥 C语言核心 \]**](https://blog.csdn.net/fengtinghuqu520/category_12955956.html) |
| [**\[ 💾 编程百度 \]**](https://blog.csdn.net/fengtinghuqu520/category_13083835.html) | [**\[ 📡 代码仓库 \]**](https://blog.csdn.net/fengtinghuqu520/article/details/147275999?spm=1001.2014.3001.5502) |
---------------------------------------
Running Process: 100% \| Latency: 0ms
*** ** * ** ***
#### 索引与导读
* [1. 继承的核心概念](#1. 继承的核心概念)
* [2. 继承的基本语法](#2. 继承的基本语法)
*
* [2)继承方式(权限继承矩阵表)](#2)继承方式(权限继承矩阵表))
* [3. 继承与模板的结合](#3. 继承与模板的结合)
*
* [3.1)类 继承 类模板](#3.1)类 继承 类模板)
* [3.2)类模板 继承 类模板](#3.2)类模板 继承 类模板)
* [3.3)模板继承的语法坑](#3.3)模板继承的语法坑)
*
* [如何破解?(三大解法)](#如何破解?(三大解法))
* [4. 基类 和 派生类 之间的转换](#4. 基类 和 派生类 之间的转换)
*
* [4.1)普通类型转换:必须加 const](#4.1)普通类型转换:必须加 const)
* [4.2)继承体系的特殊处理:不需要 const](#4.2)继承体系的特殊处理:不需要 const)
* [4.3)对象切片](#4.3)对象切片)
* [4.4)基类绝对不能赋值给派生类](#4.4)基类绝对不能赋值给派生类)
* [4.5)向下转型的安全隐患与 RTTI](#4.5)向下转型的安全隐患与 RTTI)
*
* [1)强制类型转换(极其危险)](#1)强制类型转换(极其危险))
* [2)安全的向下转型(dynamic_cast 安全转换)](#2)安全的向下转型(dynamic_cast 安全转换))
* [总结](#总结)
* [5. 继承中的作用域](#5. 继承中的作用域)
*
* [5.1)基类和派生类都有独立的作用域](#5.1)基类和派生类都有独立的作用域)
* [5.2)同名成员的"隐藏"与 基类:: 显式访问](#5.2)同名成员的“隐藏”与 基类:: 显式访问)
* [5.3)基类与派生类中关于 函数重载 的潜规则](#5.3)基类与派生类中关于 函数重载 的潜规则)
* [6. 派生类的默认成员函数](#6. 派生类的默认成员函数)
*
* [6.1)❗我们写这几个默认成员函数,首先思考几个原则性的问题](#6.1)❗我们写这几个默认成员函数,首先思考几个原则性的问题)
* [6.2)代码级剖析](#6.2)代码级剖析)
* [6.3)图表解析](#6.3)图表解析)
* [7. 实现一个不能被继承的类](#7. 实现一个不能被继承的类)
*
* [方法 1:利用构造函数的私有性(C++98 时代的"黑科技")](#方法 1:利用构造函数的私有性(C++98 时代的“黑科技”))
* [方法 2:使用 final 关键字(C++11 现代标准,最推荐)](#方法 2:使用 final 关键字(C++11 现代标准,最推荐))
* [8. 继承与友元](#8. 继承与友元)
* [9. 继承与静态成员](#9. 继承与静态成员)
* [⭕10. 单继承、多继承 及其 菱形继承问题](#⭕10. 单继承、多继承 及其 菱形继承问题)
*
* [10.1)单继承](#10.1)单继承)
* [10.2)多继承](#10.2)多继承)
* [10.3)菱形继承问题](#10.3)菱形继承问题)
* [10.4)虚继承](#10.4)虚继承)
* [10.5)多继承中的指针偏移问题](#10.5)多继承中的指针偏移问题)
* [11. 继承与组合](#11. 继承与组合)
*
* [为什么优先使用组合(Has-A)?](#为什么优先使用组合(Has-A)?)
* [什么时候必须用继承](#什么时候必须用继承)
* [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议)
## 1. 继承的核心概念
在`C++`中,继承是面向对象编程`(OOP)`的**三大基石之一(封装、继承、多态)**
* **基类 (Base Class / Parent Class)**:被继承的类,包含通用的属性和方法。
* **派生类 (Derived Class / Child Class)**:继承基类的类,可以添加新成员或修改(重写)基类的行为。
*** ** * ** ***
## 2. 继承的基本语法
```cpp
class Base { /* ... */ };
class Derived : [继承方式] Base { /* ... */ };
```
### 2)继承方式(权限继承矩阵表)
无论哪种继承方式,基类的 `private` 成员在派生类中**永远不可见**(不可直接访问)
* **继承方式改变的是基类的 `public` 和 `protected` 成员在派生类中的"降级"规则:**
| 继承方式 | 基类成员访问级别 | 在派生类中的访问级别 |
|------------------|-------------|-----------------|
| **public 继承** | `public` | 保持为 `public` |
| | `protected` | 保持为 `protected` |
| | `private` | 不可见(存在但无法直接访问) |
| **protected 继承** | `public` | 降级为 `protected` |
| | `protected` | 降级为 `protected` |
| | `private` | 不可见(存在但无法直接访问) |
| **private 继承** | `public` | 降级为 `private` |
| | `protected` | 降级为 `private` |
| | `private` | 不可见(存在但无法直接访问) |
*** ** * ** ***
## 3. 继承与模板的结合
### 3.1)类 继承 类模板
基类是一个模板,但派生类是一个普通的类
* **核心逻辑:** 在继承时,你必须给基类添加具体的类型(实例化)
```cpp
template
class Base {
public:
T data;
void print() { cout << "Base" << endl; }
};
// 派生类是普通类,但必须把基类模板的具体类型写死(比如 int)
class Derived : public Base {
public:
void doSomething() {
data = 10; // 这里的 data 已经被确定为 int 类型
print();
}
};
```
**总结:** 编译器把 Base 当作一个已经完全确定的类来对待
*** ** * ** ***
### 3.2)类模板 继承 类模板
* **核心逻辑:** 派生类本身也是一个模板。派生类需要接收类型参数,并且通常要把这个参数继续传递给基类
```cpp
template
class Base {
public:
T value;
};
template
class Derived :public Base {
public:
T getValue() {
return this->value; // 稍后解释为什么这里推荐加 this->
}
};
Derived d;
```
`return this->value;` ------------ **稍后解释为什么这里推荐加 `this->`**
*** ** * ** ***
### 3.3)模板继承的语法坑
**在普通的继承中** ,子类可以直接使用父类的 public 和 protected 成员
但在模板继承中,如果你直接使用父类的成员变量或函数,编译器会无情地报错:"**找不到该标识符**"!
```cpp
template
class Base {
public:
void foo() {}
};
template
class Derived : public Base {
public:
void bar() {
foo(); // ❌ 编译报错!找不到 foo()!
}
};
```
**为什么会这样?(底层原理)**
`C++` 编译器在解析模板时使用**两阶段名字查找**
1. **第一阶段(定义时)** :编译器检查模板的语法。此时,它不知道 `T` 是什么,因此它也不知道 `Base` 到底是个什么东西(因为你以后可能会写一个针对特定类型的模板特化,而那个特化版本里可能根本没有 `foo()` 函数)。所以,编译器拒绝去 `Base` 里查找名字
2. **第二阶段(实例化时)** :只有当你真正写出 `Derived d;` 时,编译器才开始把具体的类型代入
*** ** * ** ***
#### 如何破解?(三大解法)
为了在第一阶段安抚编译器,你需要显式地告诉编译器:"这个名字不是凭空出现的,它依赖于模板参数,去父类里找!"
1. **使用 `this->`(最推荐、最优雅):**
```cpp
void bar() { this->foo(); } // 告诉编译器,foo 是属于当前对象(派生自 Base)的成员函数
```
2. **使用 `Base::` 作用域解析:**
```cpp
void bar() { Base::foo(); } // 显式指明是从父类来的
// ▲ 警告:如果 foo() 是虚函数,这种写法会关闭多态机制(静态绑定),要小心使用!
```
3. **使用 `using` 声明**
```cpp
using Base::foo;
void bar() { foo(); }
```
*** ** * ** ***
## 4. 基类 和 派生类 之间的转换
* 我们先定义一个基础的继承体系,作为后面所有代码的演示平台:
```cpp
#include
#include
using namespace std;
// 基类
class Base {
public:
int base_data = 10;
virtual ~Base() = default; // 为了支持后面的 dynamic_cast
};
// 派生类
class Derived : public Base {
public:
int derived_data = 20;
};
```
`virtual ~Base() = default;`
只要你的类打算被别人继承,它的析构函数就必须是 `virtual` 的
这个我们后期会讲虚函数的作用
*** ** * ** ***
### 4.1)普通类型转换:必须加 const
```cpp
int a = 1;
// double& d = a; // ❌ 编译报错!
const double& d = a; // ✅ 正确。因为 int 转 double 会生成一个匿名的 double 临时变量(右值)
```
**临时变量为常量**
*** ** * ** ***
### 4.2)继承体系的特殊处理:不需要 const
```cpp
Derived derived_obj;
// ✅ 正确且安全!没有产生任何临时对象!
Base& base_ref = derived_obj;
Base* base_ptr = &derived_obj;
// 验证:它们操作的是同一块内存
base_ref.base_data = 999;
cout << derived_obj.base_data << endl; // 输出 999
```
当编译器看到 `Base& = derived_obj` 时,它没有去创建一个新的 `Base` 临时对象。**它只是在底层做了一个指针偏移,让 `base_ref `直接指向 `derived_obj` 内存布局中属于 `Base` 的那一部分** 。既然直接指向了原对象,当然可以直接修改,不需要 `const` 限制。
*** ** * ** ***
### 4.3)对象切片
```cpp
void test_object_slicing() {
Derived d;
d.base_data = 100;
d.derived_data = 200;
// ⚠️ 发生对象切片!这里调用的是 Base::Base(const Base& other)
Base b = d;
cout << b.base_data << endl; // 输出 100,基类数据被成功拷贝
// cout << b.derived_data << endl; // ❌ 编译报错!b 对象里根本没有 derived_data
}
```
**按值赋值**
* `Base b = d;`迫使编译器调用`Base`的拷贝构造函数
* 而`Base`的拷贝构造函数只认识`Base`自己的成员变量(`base_data`)
* 它对 `Derived` 特有的 `derived_data` 视而不见
*** ** * ** ***
### 4.4)基类绝对不能赋值给派生类
```cpp
void test_base_to_derived_assignment() {
Base b;
// Derived d = b; // ❌ 编译直接报错!不允许这种操作!
}
```
*** ** * ** ***
### 4.5)向下转型的安全隐患与 RTTI
* **披着 `Base` 外衣的 `Derived`**
```cpp
Base* true_derived = new Derived();
```
* **纯正的`Base`**
```cpp
Base* pure_base = new Base();
```
*** ** * ** ***
#### 1)强制类型转换(极其危险)
```cpp
Derived* p1 = (Derived*)pure_base;
```
**`p1->derived_data = 10;`** ------------\>编译通过,但运行时极其容易错误,**因为内存里根本没有`derived_data`**
#### 2)安全的向下转型(dynamic_cast 安全转换)
```cpp
Derived* unsafe_ptr = dynamic_cast(pure_base);
if (unsafe_ptr == nullptr) {
cout << "转换失败!被 RTTI 拦截,安全着陆。" << endl;
}
```
`pure_base`本体就是个 `Base`
#### 总结
当使用 `dynamic_cast` 时,`C++` 运行时系统会去查询对象虚函数表`(vtable)`旁的 `type_info` 信息(这就是 `RTTI`)
*** ** * ** ***
## 5. 继承中的作用域
### 5.1)基类和派生类都有独立的作用域
在 `C++` 编译器的眼里,全局作用域是最外层,基类作用域在里面,派生类作用域在最里面
**编译器找名字的顺序:先在 领地 B 找 -\> 找不到再去 领地 A 找 -\> 再找不到去全局找**
```cpp
class Base {
// 领地 A:Base 的作用域
};
class Derived : public Base {
// 领地 B:Derived 的作用域
};
```
*** ** * ** ***
### 5.2)同名成员的"隐藏"与 基类:: 显式访问
**因为编译器找名字是从内向外找(先找派生类,再找基类)**
一旦在派生类的作用域里找到了你要的名字,它就会立刻停止搜索。
这就导致基类里那个同名的成员被"屏蔽"了
```cpp
#include
using namespace std;
class Base {
public:
int value = 100; // 基类的 value
};
class Derived : public Base {
public:
int value = 200; // 派生类的 value,名字相同,触发隐藏!
void print() {
// 1. 直接访问,编译器在当前作用域找到了 value,直接用
cout << "直接访问 value: " << value << endl; // 输出 200
// 2. 破除隐藏:必须加上域作用限定符 Base::
cout << "显式访问 Base::value: " << Base::value << endl; // 输出 100
}
};
```
*** ** * ** ***
### 5.3)基类与派生类中关于 函数重载 的潜规则
由于基类和派生类是两个独立的作用域,所以在派生类中只要写了一个同名函数,不管参数列表是什么,基类中所有的同名函数都会被瞬间"秒杀"(隐藏),对派生类对象不再直接可见
* 我们先定义一个基类
```cpp
class Base {
public:
void func() { cout << "Base 的无参 func()" << endl; }
void func(int x) { cout << "Base 的带参 func(int)" << endl; }
};
```
* 再定义一个派生类
```cpp
class Derived : public Base {
public:
// 只要名字叫 func,基类里的所有 func 统统被屏蔽!
void func(string s) { cout << "Derived 的 func(string)" << endl; }
};
```
* 接着我们测试
```cpp
void test_function_hiding() {
Derived d;
```
❌ 编译报错!找不到无参的 func,因为被隐藏了
```cpp
d.func();
```
❌ 编译报错!找不到接受 int 的 func,即使你传了 10,它也只认 string 版本的
```cpp
d.func(10);
```
✅ 成功调用 Derived 的版本
```cpp
d.func("hello");
```
**破除隐藏的唯一方法:指名道姓**
```cpp
d.Base::func();
d.Base::func(10);
```
*** ** * ** ***
## 6. 派生类的默认成员函数
默认成员函数通常会有6个
**但是我们通常只关注前四个:构造函数、析构函数、拷贝构造函数、赋值重载**
*** ** * ** ***
### 6.1)❗我们写这几个默认成员函数,首先思考几个原则性的问题
1. 我们不写,默认生成的函数行为是什么?是否符合要求?
2. 不符合,我们要自己实现吗?如何实现?
> **问题1:**
>
> * **默认行为(编译器的铁律)**:编译器为你生成的默认函数,对待这两部分有不同的策略。
> * **对待基类部分**:无条件调用基类对应的默认函数(默认构造、默认析构、拷贝构造函数、赋值重载)。
> * **对待派生类自己的成员** :
> * 对于内置类型(`int`,指针,`char` 等):构造时不初始化(随机值);拷贝/赋值时进行无脑的字节拷贝(**浅拷贝**);析构时不作任何处理。
> * 对于自定义类型(如 `std::string`):调用它们自己的默认函数。
> * **是否符合要求?(分水岭)**
> * **符合** :如果你的派生类里只有普通的 `int`,或者像 `std::string` 这种自己会管理内存的智能对象,编译器的默认行为堪称完美,你千万不要自己手写。
> * **不符合(灾难降临)** :如果你的派生类里有**裸指针** ,并且在堆区 `new` 了一块内存(比如 `int* arr = new int[100]`)。默认的"浅拷贝"会让两个对象的指针指向同一块内存,析构时会导致 **Double Free** (重复释放内存)导致程序直接崩溃
>
> **问题2**
>
> * **结论** :必须自己实现(遵守 C++ 的 **Rule of Three / Rule of Five**:析构、拷贝构造、赋值重载,要写就一起写)。
> * **如何实现的"生死线"** :在你自己手写派生类的这些函数时,你必须**显式地**去处理"基类部分"!如果你忘了,编译器绝不会帮你填坑,它会给你塞一个默认的过去,导致极其隐蔽的 Bug。
*** ** * ** ***
### 6.2)代码级剖析
假设我们有这样一个场景:**基类 `Person`,派生类 `Student`**
```cpp
#include
#include
using namespace std;
class Person {
protected:
int m_age;
public:
Person(int age = 0) : m_age(age) { cout << "Person 构造" << endl; }
Person(const Person& p) : m_age(p.m_age) { cout << "Person 拷贝构造" << endl; }
Person& operator=(const Person& p) {
if (this != &p) m_age = p.m_age;
cout << "Person 赋值" << endl;
return *this;
}
virtual ~Person() { cout << "Person 析构" << endl; }
};
```
*** ** * ** ***
**接下来,我们手撕`Student`的四大默认函数**
1. 派生类的构造函数(先造老子,再造儿子)
* 如何实现: 必须在派生类的初始化列表中,显式调用基类的构造函数来初始化基类部分
**如果你不写,编译器会强行调用基类的无参默认构造函数(如果基类没有无参构造,直接编译报错)**
```cpp
class Student : public Person {
private:
char* m_name;
public:
// ✅ 正确姿势:通过初始化列表,把 age 传给 Person,同时初始化自己的 m_name
Student(int age, const char* name) : Person(age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
cout << "Student 构造" << endl;
}
};
```
*** ** * ** ***
2. 派生类的拷贝构造函数
* **如何实现:** 很多人自己写深拷贝时,光顾着拷贝自己的指针,忘了基类!
如果你不显式调用基类的拷贝构造,**编译器会去调用基类的默认无参构造函数!导致你新对象的 `m_age` 变成了 `0` 或者`随机值`**
❌ 错误姿势:忘了基类!导致 Person 部分被默认初始化,产生数据切片
```cpp
Student(const Student& other) { ... }
```
✅ 正确姿势:必须在初始化列表中显式调用 `Person` 的拷贝构造,传入 `other!`
💡 魔法点:这里把 Student 引用传给 Person 的引用参数,发生了我们之前讲过的"绝对安全"的向上转型!
```cpp
Student(const Student& other) : Person(other) {
// 自己动手做深拷贝
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
cout << "Student 拷贝构造" << endl;
}
```
*** ** * ** ***
3. 派生类的赋值运算符重载
* 如何实现: 赋值重载同样需要处理两层逻辑
* 一要**防止自己赋值给自己**
* 二要**显式调用基类的赋值重载,最后再处理自己的深拷贝**
```cpp
// ✅ 正确姿势
Student& operator=(const Student& other) {
cout << "Student 赋值" << endl;
// 1. 防身术:防止自己赋值给自己(极度危险,会导致自己的内存先被删掉)
if (this == &other) {
return *this;
}
// 2. 显式调用基类的赋值重载,处理基类部分的数据(age)
// 💡 魔法点:显式指明作用域
Person::operator=(other);
// 3. 处理派生类自己的深拷贝(先释放旧的,再分配新的)
delete[] m_name;
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
return *this;
}
```
*** ** * ** ***
4. 派生类的析构函数
* **如何实现:** 在析构函数里,你只需要打扫派生类自己弄脏的屋子(释放 `m_name`)。绝对不要去显式调用 `~Person()`
* **底层机制:** 编译器在执行完派生类的析构函数体后,会自动在底层插入对基类析构函数的调用。这就保证了**先灭儿子,再灭老子**的绝对顺序。如果你手贱调用了,会导致基类被析构两次
```cpp
// ✅ 正确姿势:只管好自己,剩下的交给编译器
~Student() { // 记得基类的析构函数一定要是 virtual 的!
delete[] m_name;
cout << "Student 析构" << endl;
// 编译器会自动在这里偷偷加上:Person::~Person();
}
```
*** ** * ** ***
### 6.3)图表解析
**构造时先造老子后造儿子,析构时先灭儿子后灭老子**

*** ** * ** ***
## 7. 实现一个不能被继承的类
### 方法 1:利用构造函数的私有性(C++98 时代的"黑科技")
**派生类的构造函数必须调用基类的构造函数**
如果你把基类的构造函数设为 `private`,那么派生类在实例化时,就会因为"看不见、调不动"父类的构造函数而导致编译报错
```cpp
class Base {
private:
Base() {} // 1. 构造函数私有化:只有我自己能用,别人(包括儿子)都调不到
};
class Derived : public Base {
public:
// ❌ 编译报错:'Base::Base()' is private
// 因为 Derived 的构造函数必须先调用 Base 的构造函数,但 Base 把它藏起来了
Derived() {}
};
int main() {
// Base b; // ❌ 连基类自己也没法直接在外部实例化了
// Derived d; // ❌ 派生类彻底废了
return 0;
}
```
*** ** * ** ***
### 方法 2:使用 final 关键字(C++11 现代标准,最推荐)
`C++11` 引入了`final`关键字,直接从语法层面封死了继承的路
**在类名后面直接加 final**
```cpp
// 在类名后面直接加 final
class Base final {
public:
Base() {}
};
// ❌ 编译报错:cannot derive from 'final' base 'Base' in derived type 'Derived'
class Derived : public Base {
};
```
*** ** * ** ***
## 8. 继承与友元
友元关系不能继承,也就是说**基类友元不能访问派生类私有和保护成员**
> 通俗地说:"**你父亲的朋友(基类的友元),不一定是你的朋友(派生类的友元)** 。"即便 `Display` 函数在 `Person` 里被声明为友元,它也只能看到 `Student` 对象中属于 `Person` 的那部分,而看不到 `Student` 自己特有的保护成员
```cpp
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl; // 编译报错:无法访问 protected 成员
}
int main()
{
Person p;
Student s;
// 编译报错: error C2248: "Student::_stuNum": 无法访问 protected 成员
// 解决方案: Display 也要成为 Student 的友元即可
Display(p, s);
return 0;
}
```
1. `Display` 是 `Person` 的友元 → 它可以访问 `Person` 的 `_name`
2. `Student` 继承自 `Person`
3. `Display` 试图访问 `Student` 特有的 `_stuNum`
4. **结局:编译器拒绝**
因为 `Display` 并没有在 `Student` 类里获得"好友位"。
*** ** * ** ***
## 9. 继承与静态成员
基类定义了 `static` 静态成员,则整个继承体系里面**只有一个** 这样的成员。无论派生出多少个派生类,都只有一个 `static` 成员实例
```cpp
class Person
{
public:
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum;
public:
int main()
{
Person p;
Student s;
// 非静态成员 _name 的地址是不一样的
// 说明派生类继承下来了,父类和派生类对象各有一份
cout << &p._name << endl;
cout << &s._name << endl;
// 静态成员 _count 的地址是一样的
// 说明派生类和基类共用同一份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,通过父类或派生类类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}
};
```
*** ** * ** ***
## ⭕10. 单继承、多继承 及其 菱形继承问题
### 10.1)单继承
一个派生类只从一个基类继承
```cpp
class Animal {
public:
void eat() { cout << "正在进食..." << endl; }
};
class Dog : public Animal {
public:
void bark() { cout << "汪汪汪!" << endl; }
};
```
**内存模型**
* **布局:** 内存中先排布 `Animal` 的成员,紧接着排布 `Dog` 的成员
* **关系:** 是一条直线:Animal -\> Dog
*** ** * ** ***
### 10.2)多继承
多继承允许一个派生类同时从两个或多个基类继承
```cpp
//基类 A:沙发
class Sofa {
public:
void sit() { cout << "坐着很舒服..." << endl; }
};
//基类 B:床
class Bed {
public:
void sleep() { cout << "躺着很安稳..." << endl; }
};
//派生类:沙发床
class SofaBed : public Sofa, public Bed {
public:
void fold() { cout << "正在折叠成沙发..." << endl; }
};
```
**内存模型**
* **布局** :内存中会按照继承声明的顺序,先排布 `Sofa` 的成员,再排布 `Bed` 的成员,最后是 `SofaBed` 自己的成员。
* **关系** :是一个多对一的结构: `{Sofa, Bed} -> SofaBed`
*** ** * ** ***
### 10.3)菱形继承问题
**菱形继承** :菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有**数据冗余** 和**二义性** 的问题,在 `Assistant` 的对象中 `Person` 成员会有两份

*** ** * ** ***
### 10.4)虚继承
`C++`语法复杂,多继承就是一个体现。
有了多继承,就存在菱形继承;
有了菱形继承就会有菱形虚拟继承
**我们观察下面的代码**
```cpp
class Person
{
public:
string _name; // 姓名
/*int _tel;
int _age;
string _gender;
string _address;*/
};
//使用虚继承 Person 类
class Student : virtual public Person
{
protected:
int _num; // 学号
};
//使用虚继承 Person 类
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
//教授助理
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
//使用虚继承,可以解决数据冗余和二义性
Assistant a;
a._name = "peter";
return 0;
}
```
**`virtual` 必须加在产生"分叉"的地方,而不是"汇合"的地方**
*** ** * ** ***
### 10.5)多继承中的指针偏移问题
```cpp
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;
return 0;
}
```
| 指针变量 | 指向类型 | 实际地址值 | 说明 |
|------|-----------|---------|----------------------------|
| `p3` | `Derive*` | `0x100` | 指向对象开头 |
| `p1` | `Base1*` | `0x100` | 因为 `Base1` 在开头,所以和 `p3` 相等 |
| `p2` | `Base2*` | `0x104` | 发生了偏移!跳过了 `Base1` 的空间 |
*** ** * ** ***
## 11. 继承与组合
**继承是 `is-a` 关系**
**组合是 `has-a` 关系**
```cpp
class Engine {
public:
void start() { /* 引擎启动逻辑 */ }
};
class Car {
private:
Engine _eng; // 组合:引擎是 Car 的一个成员
public:
void run() {
_eng.start(); // ✅ 黑箱复用:Car 只管用 Engine 的公开接口,不关心其内部怎么喷油的
cout << "汽车行驶中..." << endl;
}
};
```
`Engine` 内部怎么改,只要 `start()` 接口不变,`Car` 就不受影响。每个类都保持了良好的封装性
*** ** * ** ***
### 为什么优先使用组合(Has-A)?
* **组合** :宿主类只知道成员类的 `public` 接口。成员类内部怎么改(甚至改了成员变量名),只要接口不变,宿主类代码一行都不用动。
* **继承** :派生类可以看到基类的 `protected` 成员。基类一旦改动内部逻辑,往往会引发派生类的连锁崩溃。这被称为\*\*"脆弱的基类问题"\*\*
*** ** * ** ***
### 什么时候必须用继承
C++ 的灵魂------**多态**,必须依赖继承
| 场景需求 | 推荐方案 | 理由 |
|-----------------|--------|----------------------------|
| 我想借用另一个类的功能代码 | **组合** | 借用工具而已,没必要认爹,保持代码独立性。 |
| 我想在运行时更换某个组件 | **组合** | 插件式设计,极其灵活。 |
| 我需要实现接口的一致性(多态) | **继承** | 只有继承能让你在父类指针下操作不同子类。 |
| 两个类有严格的层级和分类关系 | **继承** | 符合人类直觉,逻辑清晰。 |
| 我想隐藏基类的复杂实现细节 | **组合** | 继承会暴露太多 `protected` 内容给子类。 |
*** ** * ** ***
## 💻结尾--- 核心连接协议
**警告:** 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠
*** ** * ** ***
**【📡】 建立深度链接:** **关注**本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。
**【⚡】 能量过载分发:** 执行**点赞**操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。
**【💾】 离线缓存核心:** 将本页加入**收藏**。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。
**【💬】 协议加密解密:** 在**评论区**留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。
**【🛰️】 信号频率投票:** 通过**投票**发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。
*** ** * ** ***

