%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 动态类型与静态类型
静态类型、动态类型是与变量、**特别是指针和引用相关**的两个概念,涉及到 OOP 和 **==运行时多态性==** [^1]。
- **==静态类型==**(Static Type):**变量被声明的类型**或**表达式结果的类型**,**==编译时已知==**;
- **==动态类型==**(Dynamic Type):**变量或表达式**表示的 **"内存中的对象" 的实际类型**,直到 **==运行时才能确定==**。
当且仅当一个**指针或引用**指向一个 **==多态类型==**(含有虚函数的类及其派生类)的对象时,**动态类型才会与静态类型不同**。
而 **"静态类型"决定了对象的哪些成员是可见的**。
> [!info] 静态类型
> 静态类型是**变量被声明的类型**,在**==编译期已知==**,且在变量的整个生命周期内不变。
> 编译器会根据静态类型来进行类型检查、确定变量在内存中占用的空间大小。
>
> **静态类型决定了对象==可以直接调用哪些成员函数、访问哪些成员变量==。**
> [!info] 动态类型
>
> 动态类型是**变量、表达式**所对应的 **"内存中的对象"的实际类型**,在运行时才能确定。
>
> 动态类型**在运行时可以改变**,因为指针或引用可以在它们的生命周期内指向不同类型的对象。
> [!NOTE] 静态类型与动态类型的关系
>
>
> - **静态类型用于==编译时的类型检查==**,它决定了可以通过一个变量访问哪些成员。
> - **动态类型用于==运行时行为的决定==**,即实现运行时多态——当通过基类的指针或引用调用虚函数时,实际上调用的是对象动态类型对应的最终重写版本的函数。
```cpp title:static_and_dynamic_type
Base* ptr; // ptr的静态类型是Base*
ptr = new Derived1(); // ptr此时的动态类型是Derived1*
ptr = new Derived2(); // ptr此时的静态类型是Derived2*
```
<br><br>
# 运行时类型识别 RTTI
> 运行时类型识别(run-time type identification,**RTTI**)

C++中的**运行时类型识别(RTTI)** 机制支持在 **==运行时查询和操作== 对象的 "动态类型" 信息**。
RTTI 的功能由两个操作符支持:
- **`dynamic_cast<>()` 类型转换操作符**;
- 参见 [[02-开发笔记/01-cpp/类型相关/cpp-类型转换#(2) `dynamic_cast` 操作符|cpp-类型转换#dynamic_cast运算符]]
- **`typeid` 操作符**
- 参见 [[02-开发笔记/01-cpp/类型相关/cpp-类型推导#`typeid` 运算符|cpp-类型推导#typeid运算符]]
当将这两个运算符用于**指向某种 "==多态类型=="(具有虚函数)的==指针或引用==** 时,运算符将使用指针或引用所指对象的 "**==动态类型==**"。
> [!info] `typeid` 、 `std::type_info`、`std::bad_cast` 在 `<typeinfo>` 头文件中定义
> [!example] RTTI 说明示例
>
> ```cpp title: rtti. cpp
> struct Base {
> virtual ~Base () = default;
> int base_v = 6;
> };
>
> struct Derived : Base {
> int derived_v = 15;
> };
>
> /** C++中提供了两个操作符来支持 RTTI:
> * dynamic_cast: 用于安全地实现向下转型——基类执行或引用到派生类指针或引用的转换.
> * typeid: 用户获取一个表达式的类型信息, 当用于多态类型的对象时, 返回其动态类型.
> */
>
> int main () {
> // `ptr_base` 的静态类型是"指向基类的指针", 但实际指向一个派生类对象.
> Base *ptr_base = new Derived (); // 向上转型.
>
> // 1) `dynamic_cast` 将基类指针转为派生类指针 (向下转型)
> // 在条件语句中声明向下转型, 则指针 ptr_derived 作用域为条件代码块内部
> if (Derived *ptr_derived = dynamic_cast<Derived*>(ptr_base)) {
> // 转换成功, 指针所指的实际对象确实是 Derived
> // 通过 ptr_derived 使用其指向的 Derived 对象
> cout << ptr_derived->derived_v << '\n';
> } else {
> // 转换失败, 指针所指的实际对象非 Derived
> // 通过 ptr_base 使用其指向的 Base 对象
> cout << ptr_base->base_v << '\n';
> }
>
> // `dynamic_cast` 将基类引用转换为派生类引用 (向下转型)
> Base b;
> const Base& b_ref = b;
> try {
> const Derived& d = dynamic_cast<const Derived&>(b_ref);
> } catch (std::bad_cast) {
> // 处理类型转换失败的情况.
> }
>
> // 2) `typeid` 获取对象类型信息, 返回一个 `std::type_info` 对象的引用.
> // 确定基类指针或引用实际指向的对象类型 (动态类型)
> assert (typeid (*ptr_base) == typeid (Derived));
> }
>
> ```
>
<br>
### 使用示例
使用示例:**判断两个==多态==对象是否相等**(动态类型相同,且数据成员取值完全相同)[^2]
下例中定义了一个 `operator==(const Base&, const Base&)` 并在函数体里利用 "**==多态==**" 进行**判等**
(**`typeid` 判定动态类型相等** + **调用虚函数判定数据成员相等**)。
> [!caution] **如果不像下例实现,就必须得为基类、各个派生类分别重载一个 `operator==()`**。
```cpp title:rtti_exam.cpp
/** RTTI使用示例: 比较两个多态对象是否完全相等(动态类型相同, 成员取值完全相同)
*
* 要点:
* ! 1. 基类定义一个虚函数`bool equal(const Base&);`, 比较其两个基类对象的数据成员是否完全相等
* ! 2. 其派生类重写该虚函数, 并通过向下转型, 将基类引用的形参转为派生类引用, 从而比较派生类特有的数据成员
* ! 3. 为基类定义一个友元函数, 重载`==`比较运算符, 以基类引用为形参:
* ! 1) 利用`typeid`判断两个对象的"动态类型"是否相同.
* ! 2) 函数体内利用"动态绑定"来调用实际派生类的`equal()`, 判断数据成员是否完全相同
*/
#include <typeinfo>
#include <iostream>
using namespace std;
class Base {
friend bool operator==(const Base&, const Base&);
public:
// Base的接口成员
Base(char ch) : b_ch(ch) {};
protected:
virtual bool equal(const Base&) const; // 虚函数, 用于比较该类的两个实例的据成员是否相等.
private:
char b_ch;
};
class Derived : public Base {
public:
// Derived的其他接口成员
Derived(int v, char ch) : Base(ch), d_val(v) {}
protected:
// 派生类重写的虚函数, 形参涉及基类类型时, 不可变;
// 形参只有基类部分, 故该虚函数不足以判断两个Derived对象相等,
// 外部还需 typeid 辅助, 首先确定两个对象的动态类型一致, 才能再调用该equal判等.
bool equal(const Base&) const override;
private:
int d_val;
};
bool operator==(const Base& lhs, const Base& rhs) { //
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs); // `lhs.equal()`为运行时多态, 动态绑定
}
bool Base::equal(const Base& rhs) const { // 比较确定基类部分的数据成员完全相等
return b_ch == rhs.b_ch;
}
bool Derived::equal(const Base& rhs) const { // 比较确定派生类特有部分的数据成员完全相等
// 这是对虚函数的重写, 所以形参列表必须同基类的虚函数, 只能是基类的引用.
// 但这里可进行向下转型, 将基类引用转换为派生类引用, 从而可以访问派生类的数据成员.
auto r = dynamic_cast<const Derived&>(rhs);
return Base::equal(rhs) && (d_val == r.d_val);
}
int main() {
Base b1('A'), b2('C');
Derived d1(6, 'G'), d2(6, 'G');
if (b1 != b2) cout << "b1 != b2" << endl;
if (d1 == d2) cout << "b1 == b2" << endl;
}
```
<br><br>
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P534
[^2]: 《C++ Primer》P734