%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # Union 联合体 Union 是一种特殊的类,其可具有**多个数据成员**,但 **==任意时刻只有其中一个数据成员有效==**。 当**为 union 对象的某个成员赋值**后,其他成员就**变为未定义状态**。 ##### union 类的特性 - union 成员: - **可以是具有构造函数和析构函数的==类类型==**, - **不能含有==引用==类型** - union **可以定义包括构造函数、析构函数在内的==成员函数==**; - union 中可指定`public`,`protected` ,`private` 等**访问说明符**,其 **==默认为 "`public`"的==** ; - union **不能派生自其它类**,**也不能作为基类**,**不能含有虚函数**。 > [!NOTE] union 中的各成员可视作为是**对 "==同一内存空间==" 的不同解释**。 > > 故**一个 union 对象所占有的内存,至少需要能存储其最大的数据成员**。 > [!tip] 为追踪 union 中当前存储的值类型,通常会定义一个独立的对象,**==标识 union 中存储的值==**。 > > 该独立对象称之为**该 union 的判别式**(discriminant)。 <br><br> # union 对象的初始化 union 对象的初始化有几种方式: - (1)使用**列表初始化 `{}`**,该初始值默认将用于 "**初始化其==第一个成员==**"。 - (2)使用**聚合初始化**中的 "**==指定初始化项列表==**",可**指定被初始化的成员**,即 `{ .mem = val}` 的形式。 - 参见 [[02-开发笔记/01-cpp/cpp 基本概念/cpp-初始化声明#聚合初始化(Aggregate Initialization)|cpp-初始化声明#聚合初始化]] - (3)定义 **构造函数** & 使用 **直接初始化 `()` 语法**,**显式初始化某个成员**。 > [!caution] **未显式初始化**时,一个 union 对象**默认是 "==未初始化==" 的**。 方式一 & 方式二: ```cpp union Data { int iVal; double dVal; }; int main() { Data d1 = { 33 }; // 默认会初始化其 "第一个成员" Data d2 = { .dVal = 44 }; // 聚合初始化, 支持指定 "被初始化的成员" return 0; } ``` 方式三:构造函数 & 直接初始化 ```cpp union Data { int iVal; double dVal; Data(int v) : iVal(v) { cout << "iVal" << endl; } Data(double d) : dVal(d) { cout << "dVal" << endl; } }; int main() { Data d1(42); // 调用构造函数`Data(int)`, 初始化iVal Data d2(3.14); // 调用构造函数`Data(doble)`, 初始化dval return 0; } ``` <br><br> # union 对象的赋值 - 当 union 中只包含 "**==基本数据类型==**" 时,使用常规赋值语句为 "**指定成员**" 进行赋值即可。 - 当 union 中包含 "**==类类型==**" 时, - 将 union 对象由 "**类类型成员的值**" 改为 "**其他值**" 时,必须先调用该类类型的 "**==析构函数==**"。 - 将 union 对象由 "**其他值**" 改为 "**类类型成员的值**" 时,必须调用该类类型的 "**==构造函数==**"。 > [!tip] 通常将具有 "类类型成员" 的 union 封装到一个 "管理类" 中。 > > 为了对具有 "**类类型成员的 union**" 实现赋值&初始化过程中对 "**==类类型成员的构造/析构==**" 的管理, > 通常是**将 union 放到一个常规类中为一个 "==匿名 union=="**,**由该类则负责提供相关管理操作**,例如**重载赋值运算符** 等。 ### 使用示例 示例:**通过封装一个类来==管理具有 "类类型成员" 的 union==** ```cpp /* 示例: 使用一个类来管理匿名union. * * 思想: 将匿名Union放入一个类中, 而该类的作用则是实际管理Union中与类类型成员有关的状态转换. * * 每个Token对象含有一个匿名union类型的未命名成员. * Token类实际上是为 "管理该匿名union类型" 而封装的. */ class Token { public: Token(int v = 0) : tok(INT), ival{v} {} Token(char ch) : tok(CHAR), cval{ch} {} Token(double db) : tok(DBL), dval{db} {} Token(const std::string& str): tok(STR) { new (&sval) std::string(str); } Token(std::string&& str): tok(STR) { new (&sval) std::string(std::move(str)); } Token(const Token&); Token(Token&&); Token& operator=(const Token&); Token& operator=(Token&&); ~Token() { if (tok == STR) { sval.~string(); // 对于union中的类类型成员, 必须显式析构. } } // 下面的赋值运算符, 负责设置union的不同成员 Token& operator=(const std::string&); Token& operator=(std::string&&); Token& operator=(char); Token& operator=(int); Token& operator=(double); private: // 检查判别式, 根据情况来拷贝union成员. void copyUnion(const Token&); void copyUnion(Token&&); enum { INT, CHAR, DBL, STR } tok; // 枚举值用作union的判别式, 确定union存储的值类型. union { // 匿名Union char cval; int ival; double dval; std::string sval; }; }; Token::Token(const Token& rhs) : tok(rhs.tok) { copyUnion(rhs); } Token::Token(Token&& rhs) : tok(rhs.tok) { copyUnion(std::move(rhs)); } void Token::copyUnion(const Token& rhs) { switch (rhs.tok) { case INT : ival = rhs.ival; break; case CHAR: cval = rhs.cval; break; case DBL : dval = rhs.dval; break; case STR : { // 使用定位new, 在Union对象地址上调用类类型的构造函数完成初始化. new (&sval) string(rhs.sval); } break; } } void Token::copyUnion(Token&& rhs) { switch (rhs.tok) { case INT : ival = rhs.ival; break; case CHAR: cval = rhs.cval; break; case DBL : dval = rhs.dval; break; case STR : { // 使用定位new, 在Union对象地址上调用类类型的构造函数完成初始化. new (&sval) string(std::move(rhs.sval)); } break; } } Token& Token::operator=(const Token& rhs) { if (this != &rhs) { if (tok == STR && rhs.tok != STR) { sval.~string(); } if (tok == STR && rhs.tok == STR) { sval = rhs.sval; } else { copyUnion(rhs); } tok = rhs.tok; } return *this; } Token& Token::operator=(Token&& rhs) { if (this != &rhs) { if (tok == STR && rhs.tok != STR) { sval.~string(); } if (tok == STR && rhs.tok == STR) { sval = std::move(rhs.sval); } else { copyUnion(std::move(rhs)); } tok = rhs.tok; } return *this; } Token& Token::operator=(const std::string& str) { if (tok == STR) { sval = str; } else { new (&sval) string(str); tok = STR; } return *this; } Token& Token::operator=(std::string&& str) { if (tok == STR) { sval = std::move(str); } else { new (&sval) string(std::move(str)); tok = STR; } return *this; } Token& Token::operator=(int val) { if (tok == STR) sval.~string(); ival = val; tok = INT; return *this; } Token& Token::operator=(char val) { if (tok == STR) sval.~string(); cval = val; tok = CHAR; return *this; } Token& Token::operator=(double val) { if (tok == STR) sval.~string(); dval = val; tok = DBL; return *this; } int main() { Token token("Hello World"), tk2(6.828), tk3(tk2); token = 25; token = 3.14; token = "JJKKHHLL"; token = tk2; token = std::move(tk3); } ``` <br><br><br> # 匿名 Union 匿名 union 是一个**未命名的 union**,编译器会**自动为匿名 union 创建一个==未命名的对象==**。 在**该匿名 union 所在作用域**内,其**内部成员均可直接被访问**。 > [!caution] 匿名 Union 中不能包含 `protected` 成员或 `private` 成员,也不能定义成员函数。 ```cpp union { // 定义了一个匿名Union, 编译器会自动为其创建一个未命名对象. char cval; int ival; double dval; }; cval = 'c'; // 为匿名union对象赋予一个新值. ival = 42; // 该对象当前保存值是42 ``` <br> ### 使用示例 示例:**匿名 union**通常用于**一个类类型的内部**,从而限定作用域。 ```cpp struct Container { union { int intValue; double doubleValue; }; }; int main() { Container c; c.intValue = 10; c.doubleValue = 20.5; } ``` <br><br> # 参考资料 # Footnotes