计算机
c base
c++ 基础 作用域及生命周期
c++ template
c++ 内存视角
c++ 函数
c++ 基础 基础语法
c++ 性能
c++ 类 基础
c++ 类 对象模型 类析构
c++ 类 设计模式
C++ STL
cmake
CMAKE环境搭建 windows
创建第一个cmakelists.txt
构建稍复杂的项目
动态链接库
EX1
EX1 START
EX1 ANSWER
EX2
EX2 START
EX2 ANSWER
EX3
EX3 START
EX3 ANSWER
变量
控制流程
函数和宏
查找和使用外部库
生成器表达式
qt
qt c++
qt index
qt qml quick
qt ui
qt 多线程
理解QObject 1
理解QObject 4
C++ 技巧 反射
理解QObject 2
理解QObject 3
理解QObject 6
理解 QObject 5
QCoreApplication
QApplication
数据结构
PC问题监控及排查
PC程序性能优化
OS
TOOL
编程漫谈
sealos+frp,搭建内网穿透
host主机配置
C++实战 主题
多线程
生产者-消费者流水线
IO
网络
Bilinotes搭建
编译原理
WEB开发
TS
HTML CSS JAVASCRIPT
本站点使用 MrDoc 构建
-
+
c++ 类 对象模型 类析构
"发布于 2025-02-24 13:57" class对象的生命周期: class{ function member } 构造、析构、拷贝、赋值 (内存变化) # 对象模型 (2种)class data member: static nonstatic (3种)class member functions: static nonstatic virtual  1. 数据成员(Data Members) | 类型 | 存储位置 | 生命周期 | 访问方式 | 示例 | | --- | --- | --- | --- | --- | | static | 全局数据区(独立于对象) | 程序运行期间 | `类名::成员名` 或 `对象.成员` | `static int count;` | | non-static | 对象内存中 | 随对象创建/销毁 | `对象.成员名` | `int value;` | 2. 成员函数(Member Functions) | 类型 | 是否依赖对象实例 | 能否访问非静态成员 | 能否被重写(override) | 示例 | | --- | --- | --- | --- | --- | | static | ❌ 不依赖 | ❌ 不能 | ❌ 不能 | `static void print();` | | non-static | ✅ 依赖 | ✅ 能 | ❌ 不能(除非是虚函数) | `void show();` | | virtual | ✅ 依赖 | ✅ 能 | ✅ 能(支持多态) | `virtual void draw();` | | 类的组成部分 | 存储位置 | 说明 | | --- | --- | --- | | 成员函数(非虚) | 代码段(.text 区) | 所有对象共享同一份函数代码,编译后固定在可执行文件中。 | | 虚函数(virtual) | 虚函数表(vtable) | 存储在只读数据段(.rodata 或 .data),供所有对象共享。 | | 静态成员变量 | 全局/静态数据区(.data/.bss) | 独立于对象存在,程序启动时初始化。 | | RTTI(运行时类型信息) | 只读数据段 | 用于 typeid 和 dynamic_cast(如果有虚函数)。 | # 初始化 | 特性 | 平凡(Trivial) | 非平凡(Non-Trivial) | | :--- | :--- | :--- | | **操作方式** | 按字节拷贝/移动 | 需要执行函数代码 | | **执行代码** | 不执行任何语句 | 执行构造函数/析构函数代码 | | **内存处理** | 位拷贝(bitwise copy) | 深拷贝或资源管理 | | **性能** | 极高(可优化) | 相对较低 | | **编译器处理** | 如同C结构体 | 必须遵守C++规则 | # default construct | 类特征 | 隐式声明 | 是否合成 | 平凡性 | 合成构造执行的操作 | |:---|:---:|:---:|:---:|:---| | **仅含基本类型成员(int, double, 指针等)** | ✅ 是 | ❌ 否(按需) | ✅ trivial | 什么都不做,成员保持未初始化 | | **含有带默认构造的member object** | ✅ 是 | ✅ 是(按需) | ❌ nontrivial | 按声明顺序调用各member的默认构造 | | **继承自带默认构造的基类** | ✅ 是 | ✅ 是(按需) | ❌ nontrivial | 调用基类的默认构造 | | **声明了虚函数** | ✅ 是 | ✅ 是(按需) | ❌ nontrivial | 初始化vptr指向类的vtbl | | **有虚基类** | ✅ 是 | ✅ 是(按需) | ❌ nontrivial | 初始化虚基类指针/偏移信息 | | **成员有默认成员初始化器(int x = 0)** | ✅ 是 | ✅ 是(按需) | ❌ nontrivial | 执行默认成员初始化 | (from C++ Standard) 当类没有任何用户声明的构造函数时,编译器确实会隐式声明一个默认构造函数。这个隐式声明的默认构造函数是inline public的。 对于Class X,如果没有任何user-declared constructor,那么会有一个default constrcutor被implicitly声明,一个被implicitly声明的default constructor将是一个trivial construcor trival constructor意味着生成的构造函数不进行任何操作。 ## nontrivial default constructor 如果该类型在构造时可能分配内存、加锁、调用外部API等,那它几乎肯定是非平凡的。 如果它只是简单聚合数据(如POD类型),则可能是平凡的 如果class没有任何constructor,但是内含一个member object,而后者是default constructor,这个class的implicit default constructor就是"nontrivial",编译器需要为此class合成一个default constructor,不过合成操作只有再constructpr真正需要被调用的时候才会发生。 编译器为避免合成出多个default constructor,解决方法是把合成的deafult constructor、copy constructor、destructor、assignment copy operator都以inline的方式完成,一个inline函数有静态链接,不会被档案以外者看到,如果函数太复杂,会合成一个explicit non-line static实体 被合成的default constructor只满足编译器的需要,而不是程序的需要 ## user constrcutor user所提供的constructors存在,所以不会合成新的default constructor。如果同时存在着"带有default constructors"的member class objects,那些default constructors也会被调用 ## have virtual function class 继承或声明一个 virtual function class 派生自一个继承串链,其中有一个或者更多的virtual base classes 由于缺乏user声明的constructor,编译器会详细记录合成一个default constructor的必要信息: virtual function table : 内放class的virtual function地址 每一个class object中,一个额外的pointer member被编译,内含相关的class vtbl地址 virtual invocation会被重写 对于那些未声明的任何constructors的class,编译器会为它们合成一个default constructor,以便正确的初始化每一个class object的vptr ## have virtual base class 虚基类的构造函数由最派生类直接调用 中间类(如A)对虚基类的构造调用可能被覆盖 成员变量(如j)的初始化与普通类一致,不受虚继承影响 virtual base class的实现法在不同的编译器之间有极大的差异,共同点在于必须使virtual base class在其每一个derived class object中的位置,能够在执行期间准备妥当 ```cpp class X {public: int i;}; class A : public virtual X {public: int j;}; class B : public virtual X {public: double d;}; class C : public A, public B {public: int k;}; void foo(const A\* pa){pa\->i = 1024} void foo(const A\* pa) {pa\->\_\_vbcX\->i = 1024;} ``` # copy constructor | 情况 | 是否合成 | 是否调用 | 说明 | | :--- | :---: | :---: | :--- | | **位拷贝语义** | ❌ 不合成 | ❌ 不调用 | 直接memcpy | | **成员有自定义拷贝** | ✅ 合成 | ✅ 调用 | 必须调用成员拷贝 | | **基类有自定义拷贝** | ✅ 合成 | ✅ 调用 | 必须调用基类拷贝 | | **有虚函数** | ✅ 合成 | ✅ 调用 | 必须正确复制vptr | | **有虚基类** | ✅ 合成 | ✅ 调用 | 必须处理虚基类 | | **显式=delete** | ❌ 不合成 | ❌ 不可用 | 拷贝被禁止 | | **显式=default** | ✅ 合成 | ✅ 调用 | 强制生成 | 1 Class X {...} ; 对一个object做明确的初始化工作 X x; X xx = x 2 object被当作参数 extern void foo(X x) 3 object被当作返回值 X foo_bar{ X xx; //... return xx; } X::X(const X& x); 当一个class object以另一个同类实体作为初值时,上述的constructor会被调用。这可能会导致一个暂时性的class object的产生或优化代码 ## deafult memberwise initalizator 当class object以"相同class的另一个object"作为初值时,其内部是以所谓的default memberwise initializatior完成的 拷贝其内建的member_occurs,然后再于String member object_word身上递归实施memberwise initialization c++ standard 把copy constructor区分为trivial和nontrivial两种,只有nontrivial的实体才会被合成于程序中,决定一个copy constructor是否为trivial的标准在于class是否展现“Bitwise Copy Semantics” 一个class不展现出"bitwise copy semantics": 1 class内含mamber object,而后者的class声明有一个copy constructor ```csharp class A { public: int\* ptr; A(const A& other) { // 自定义拷贝构造 ptr = new int(\*other.ptr); // 深拷贝 } }; ``` 2 class继承自一个base class,而后者存在一个copy constructor 3 class声明了一个或多个virtual functions 4 class派生自一个继承串链,其中有一个或者多个virtual base classes ## virtual functions ## virtual base class subobject 一个class object如果以另一个object作为初值,而后者有一个virtual base class subobject,那么也会使"bitwise copy semantics"失效 为了正确处理"以一个class object"作为另一个class object的初值,必须合成出一个copy constructor ## explicit initialization 必要的程序转化分两段: 1 重写每一个定义,其中的初始化操作会被剥除 2 class的copy constructor调用操作会被安插进去 ## 参数初始化 策略1:__temp object 以class X的copy constructor正确设置初值,然后再以biewise方式拷贝到局部实体,形式参数从原来的class X object转换为class X reference;class X声明一个destructor, 策略2: 拷贝构建 该位置视函数活动范围的不同记录于程序堆栈中 ## 返回值初始化 1 加额外参数,类型是class object的一个reference 2 return指令前安插一个copy constructor # 移动构造 | 情况 | 是否隐式声明 | 是否合成 | 是否调用 | 说明 | | :--- | :---: | :---: | :---: | :--- | | **无用户声明拷贝/移动/析构** | ✅ 是 | ✅ 按需 | ✅ 是 | 隐式声明为trivial(若成员支持) | | **有用户声明拷贝构造** | ❌ 否 | ❌ 否 | N/A | 移动构造被隐式删除 | | **成员有non-trivial移动** | ✅ 是 | ✅ 按需 | ✅ 是 | 合成nontrivial移动构造 | | **显式 = default** | ✅ 是 | ✅ 是 | ✅ 是 | 强制生成 | | **显式 = delete** | ✅ 是 | ❌ 否 | ❌ 不可用 | 禁止移动 | | **只有移动构造无拷贝** | ✅ 是 | ✅ 是 | ✅ 是 | 拷贝构造被隐式删除 | | **const成员** | ❌ 否 | ❌ 否 | N/A | 移动被隐式删除 | | **引用成员** | ❌ 否 | ❌ 否 | N/A | 移动被隐式删除 | 是否显式声明了移动构造? ├─ 是 → 使用用户定义 └─ 否 → 检查抑制条件 ├─ 有用户声明拷贝构造? → ❌ 隐式删除 ├─ 有用户声明拷贝赋值? → ❌ 隐式删除 ├─ 有用户声明析构(C++11)? → ❌ 隐式删除 ├─ 有用户声明移动赋值? → ❌ 隐式删除 ├─ 有const成员? → ❌ 隐式删除 ├─ 有引用成员? → ❌ 隐式删除 ├─ 基类/成员移动构造被删除/不可访问? → ❌ 隐式删除 └─ 无抑制条件 → ✅ 隐式声明(按需合成) # 析构函数 | 情况 | 是否隐式声明 | 是否合成 | 是否调用 | 说明 | | :--- | :---: | :---: | :---: | :--- | | **无任何用户声明析构** | ✅ 是 | ✅ 按需 | ✅ 是 | 隐式声明为trivial(若成员支持) | | **用户声明析构(非虚)** | N/A | 用户提供 | ✅ 是 | 用户定义,编译器不再隐式生成 | | **用户声明虚析构** | N/A | 用户提供 | ✅ 是 | 支持多态删除 | | **显式 = default** | ✅ 是 | ✅ 是 | ✅ 是 | 强制生成,平凡性取决于成员 | | **显式 = delete** | ✅ 是 | ❌ 否 | ❌ 不可用 | 禁止析构(危险!) | # 赋值操作符 # 用户构造 # 虚函数 ## 虚函数合成规则完整对比表 ### vtbl(虚函数表)合成规则 | 情况 | 是否合成vtbl | vtbl数量 | 说明 | | :--- | :---: | :---: | :--- | | **没有任何虚函数** | ❌ 否 | 0 | 不需要多态支持 | | **有1个或多个虚函数** | ✅ 是 | 1个/类 | 无论多少虚函数,只有一个vtbl | | **继承自无虚函数的基类,自身有虚函数** | ✅ 是 | 1个/类 | 新合成vtbl | | **继承自有虚函数的基类,未覆盖** | ✅ 是 | 1个/类 | 共用/扩展基类vtbl | | **继承自有虚函数的基类,覆盖部分** | ✅ 是 | 1个/类 | 替换被覆盖的函数地址 | | **多继承(多个基类有虚函数)** | ✅ 是 | 多个 | 每个有虚函数的基类对应一个vtbl | ### vptr(虚指针)合成规则 | 情况 | 是否合成vptr | vptr数量/对象 | 初始化时机 | | :--- | :---: | :---: | :--- | | **没有任何虚函数** | ❌ 否 | 0 | N/A | | **有虚函数(单继承)** | ✅ 是 | 1 | 构造函数中初始化 | | **有虚函数(多继承,多个基类有虚函数)** | ✅ 是 | 多个(基类个数) | 每个基类构造时分别初始化 | | **虚继承 + 虚函数** | ✅ 是 | 1 + 虚基类偏移 | 构造函数中初始化 | ### 构造函数合成规则(因虚函数触发) | 条件 | 是否必须合成构造 | 合成构造执行的操作 | | :--- | :---: | :--- | | **类无用户构造,但有虚函数** | ✅ 是 | 初始化vptr指向类的vtbl | | **类有用户构造,也有虚函数** | ❌ 否(但编译器会扩展) | 在用户构造代码前插入vptr初始化 | | **派生类有虚函数,基类也有虚函数** | ✅ 是(若无用户构造) | 初始化vptr + 调用基类构造 | ### 拷贝构造函数合成规则(因虚函数触发) | 情况 | 是否合成拷贝构造 | 复制vptr的行为 | | :--- | :---: | :--- | | **有虚函数,无用户拷贝构造** | ✅ 是 | 复制源对象的vptr值(指向同一vtbl) | | **有虚函数,用户定义了拷贝构造** | ❌ 否 | 用户需自己确保vptr正确(通常自动) | | **派生类有虚函数** | ✅ 是 | 复制派生类的vptr,而非基类vtbl | ### 析构函数合成规则(因虚函数触发) | 情况 | 是否合成析构 | 说明 | | :--- | :---: | :--- | | **有虚函数,无用户析构** | ✅ 是 | 合成析构并正确设置vptr | | **有虚函数,有用户析构** | N/A | 编译器会扩展用户析构,重置vptr | | **基类有虚析构** | ✅ 是 | 派生类析构被标记为虚 | ### 虚函数调用转换规则 | 调用方式 | 编译时绑定 | 运行时绑定 | 转换后的形式 | | :--- | :---: | :---: | :--- | | **对象直接调用(非指针/引用)** | ✅ 是 | ❌ 否 | 直接调用函数地址 | | **指针/引用调用虚函数** | ❌ 否 | ✅ 是 | `(*ptr->vptr[index])(ptr)` | | **通过基类指针调用** | ❌ 否 | ✅ 是 | 同上,通过vptr索引 | | **构造函数中调用虚函数** | ✅ 是 | ❌ 否 | 静态绑定(此时vptr未完全初始化) | | **析构函数中调用虚函数** | ✅ 是 | ❌ 否 | 静态绑定(vptr已被重置) | ### 多继承下的虚函数规则 | 情况 | vtbl数量 | vptr数量 | 函数地址存储 | | :--- | :---: | :---: | :--- | | **派生类继承两个有虚函数的基类** | 3(2基类+1派生类) | 2(每个基类对应一个) | 派生类新函数放在第一个vtbl | | **派生类覆盖部分基类虚函数** | 3 | 2 | 各vtbl中对应条目被替换 | | **派生类增加新虚函数** | 3 | 2 | 放在第一个vtbl末尾 | ### 虚继承下的虚函数规则 | 情况 | 特殊组件 | 说明 | | :--- | :--- | :--- | | **虚基类有虚函数** | vbptr(虚基类指针) | 需要通过vbptr找到虚基类子对象 | | **最派生类** | 负责初始化虚基类vptr | 避免重复初始化 | | **中间类访问虚基类虚函数** | 通过vbptr间接访问 | 位置在运行时确定 | ### 编译器差异 | 编译器 | vtbl生成时机 | vptr位置 | 虚调用优化 | | :--- | :--- | :--- | :--- | | **GCC** | 编译单元内首次需要时 | 对象起始位置 | 可推测去虚拟化 | | **Clang** | 同GCC | 对象起始位置 | 支持-fforce-emit-vtables | | **MSVC** | 每个编译单元独立生成,链接器合并 | 对象起始位置(单继承) | 支持__declspec(novtable) | ### 触发合成的关键条件总结 | 条件 | 合成vtbl | 合成vptr | 合成/扩展构造 | 合成析构 | | :--- | :---: | :---: | :---: | :--- | | **至少一个虚函数** | ✅ | ✅ | ✅ | ✅ | | **继承有虚函数的基类** | ✅ | ✅ | ✅(若需要) | ✅(若需要) | | **多继承多个有虚函数的基类** | ✅(多个) | ✅(多个) | ✅ | ✅ | | **虚继承 + 虚函数** | ✅ | ✅ + vbptr | ✅ | ✅ | | **纯虚函数(抽象类)** | ✅ | ✅ | ✅ | ✅ | ### 代码示例速查 | 类定义 | 对象大小 | 虚函数表 | 说明 | | :--- | :---: | :---: | :--- | | `struct A { int x; };` | 4 | ❌ 无 | 无虚函数 | | `struct B { virtual void f(); };` | 16(8vptr+4x+对齐) | ✅ 1个 | 有虚函数 | | `struct C : B { void f() override; };` | 16 | ✅ 1个(扩展B的vtbl) | 单继承覆盖 | | `struct D : B { virtual void g(); };` | 16 | ✅ 1个(扩展) | 单继承新增 | | `struct E : B, C { };` | 32(两个vptr+成员) | ✅ 2个 | 多继承 | | `struct F : virtual B { };` | 16+8(vptr+vbptr+对齐) | ✅ 1个 | 虚继承 | # 虚基类 ## 虚基类(Virtual Base Class)完整规则表 ### 虚基类 vs 普通继承对比表 | 特性 | 普通继承 | 虚继承 | | :--- | :--- | :--- | | **基类子对象数量** | 多个副本(菱形继承时) | 单个共享副本 | | **内存占用** | 较大(重复存储) | 较小(共享) | | **访问二义性** | 需要作用域限定 | 无二义性 | | **构造函数调用** | 由直接基类调用 | 由最派生类直接调用 | | **初始化顺序** | 按声明顺序 | 虚基类优先 | | **对象布局** | 基类子对象连续排列 | 通过指针/偏移间接访问 | | **性能开销** | 无额外开销 | 间接访问,有轻微开销 | ### 虚继承的内存布局差异 | 情况 | 对象大小 | 额外组件 | 说明 | | :--- | :--- | :--- | :--- | | **普通继承(单链)** | 基类成员 + 派生类成员 | 无 | 连续存储 | | **普通继承(菱形)** | 2份基类副本 | 无 | 数据冗余 | | **虚继承(单链)** | 基类成员 + 派生类成员 + vbptr | vbptr(虚基类指针) | 间接访问 | | **虚继承(菱形)** | 1份基类副本 | vbptr | 共享虚基类 | ### 构造函数的调用规则表 | 情况 | 谁调用虚基类构造 | 谁调用普通基类构造 | 说明 | | :--- | :--- | :--- | :--- | | **虚继承(最派生类对象)** | 最派生类 | 直接基类 | 最派生类负责初始化虚基类 | | **虚继承(中间类对象)** | 中间类 | 直接基类 | 作为独立对象时,自己负责 | | **普通继承** | N/A | 直接基类 | 层层向上调用 | | **多级虚继承** | 最派生类 | 各直接基类 | 虚基类只初始化一次 | ### 初始化顺序规则表 | 优先级 | 初始化类型 | 说明 | | :---: | :--- | :--- | | 1 | **虚基类** | 按继承顺序,从左到右,深度优先 | | 2 | **直接基类** | 按声明顺序 | | 3 | **成员对象** | 按声明顺序 | | 4 | **派生类本身** | 执行构造函数体 | ### vbptr(虚基类指针)合成规则 | 情况 | 是否合成vbptr | vbptr数量 | 说明 | | :--- | :---: | :---: | :--- | | **使用虚继承** | ✅ 是 | 1个/虚基类 | 指向虚基类子对象 | | **多级虚继承** | ✅ 是 | 多个 | 每层可能需要独立vbptr | | **虚继承 + 虚函数** | ✅ 是 | vbptr + vptr | 两者独立存在 | | **不使用虚继承** | ❌ 否 | 0 | 无额外指针 | ### vbtbl(虚基类表)合成规则 | 情况 | 是否合成vbtbl | 存储内容 | 说明 | | :--- | :---: | :--- | :--- | | **使用虚继承** | ✅ 是 | 虚基类偏移量 | 用于定位虚基类子对象 | | **多级虚继承** | ✅ 是 | 多个偏移量 | 支持运行时定位 | | **单继承(无虚继承)** | ❌ 否 | N/A | 不需要间接访问 | ### 不同编译器实现差异 | 编译器 | 实现方式 | 特点 | | :--- | :--- | :--- | | **GCC** | vbptr + vbtbl | 通过偏移表定位 | | **Clang** | 同GCC | 兼容GCC ABI | | **MSVC** | vbptr + 偏移量 | 直接在vbptr中存储偏移 | ### 性能影响对比表 | 操作 | 普通继承 | 虚继承 | 性能差异 | | :--- | :--- | :--- | :--- | | **访问基类成员** | 直接偏移访问 | 通过vbptr间接访问 | 虚继承慢约1-2个指令 | | **对象构造** | 线性调用 | 需要记录初始化状态 | 虚继承有额外开销 | | **对象大小** | 较小 | 较大(vbptr开销) | 虚继承多8字节/虚基类 | | **类型转换** | 编译时偏移计算 | 运行时偏移查表 | 虚继承有运行时开销 | ### 虚继承的适用场景 | 场景 | 是否推荐使用虚继承 | 原因 | | :--- | :---: | :--- | | **菱形继承(钻石问题)** | ✅ 必须 | 避免基类重复 | | **接口多继承** | ❌ 不需要 | 通常纯虚类,无数据成员 | | **性能敏感代码** | ❌ 避免 | 有额外间接访问开销 | | **深层继承链** | ⚠️ 谨慎 | 增加复杂度和内存 | | **标准库实现(如iostream)** | ✅ 使用 | 解决菱形继承 | ### 代码示例速查表 | 代码 | 虚基类 | 对象包含 | 说明 | | :--- | :---: | :--- | :--- | | `struct A { int x; };` | ❌ | x | 普通类 | | `struct B : virtual A { int y; };` | ✅ | vbptr + y + A | 虚继承,间接访问A | | `struct C : virtual A { int z; };` | ✅ | vbptr + z + A | 另一条虚继承路径 | | `struct D : B, C { int w; };` | ✅ | 共享一份A | 菱形继承正确解决 | | `struct E : B { int w; };` | ✅ | vbptr + w + y + A | 单链虚继承 | ### 常见陷阱与解决方案 | 问题 | 原因 | 解决方案 | | :--- | :--- | :--- | | **构造函数调用混乱** | 虚基类由最派生类调用 | 在最派生类初始化列表中显式调用 | | **中间类不能初始化虚基类** | 中间类的构造调用被忽略 | 不需要在中间类初始化虚基类 | | **类型转换不确定** | 运行时位置不确定 | 使用`dynamic_cast` | | **内存布局不可预测** | 编译器实现差异 | 避免依赖具体布局 | | **性能下降** | 间接访问开销 | 仅在必要时使用虚继承 | ### 关键要点总结表 | 要点 | 说明 | | :--- | :--- | | **核心目的** | 解决菱形继承中的重复基类问题 | | **共享机制** | 所有派生类共享同一个虚基类子对象 | | **构造责任** | 最派生类负责构造虚基类 | | **初始化顺序** | 虚基类最先初始化 | | **访问方式** | 通过vbptr间接访问 | | **内存开销** | 额外的指针(vbptr)和可能的偏移表 | | **性能代价** | 间接访问带来轻微性能损失 | | **适用场景** | 菱形继承、需要共享基类状态的场景 | # 其他初始化方法
peipeo
2026年5月21日 22:04
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码