%%
# 纲要
> 主干纲要、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