%% # 纲要 > 主干纲要、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**) ![image-20240119190750841|811](_attachment/02-开发笔记/01-cpp/类型相关/运行时类型识别%20RTTI.assets/IMG-运行时类型识别%20RTTI-35C5EA79E6F3A21FA4F003BFDBF5818E.png) 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