联合体

联合体是一种用户定义的类型,由位于同一内存区域的字段组成,原因是这些字段相互重叠。这样就可以将一种类型的值写入联合体,然后在另一种类型的解释中读取其内部表示(位级)。因此,我们可以提供从一种类型到另一种类型的非标准转换。

联合体字段可以是任何内置类型,但字符串、动态数组和指针除外。此外,在联合体中,可以使用具有相同简单字段类型且不带构造函数/析构函数的结构体。

编译器会为联合体分配一个内存单元,其大小等于所有元素类型的最大大小。因此,对于具有 long(8 字节)和 int(4 字节)等字段的联合体,将分配 8 字节。

联合体的所有字段都位于相同的内存地址,即在联合体的起始位置对齐(偏移量为 0,可以使用 offsetof 进行检查,参见 打包结构体一节)。

描述联合体的语法与描述结构体类似,但使用的是 union 关键字。它的后面是一个标识符,然后是一个包含字段列表的代码块。

例如,某种算法可能会使用 double 类型的数组来存储各种设置,只因为 double 类型是最大字节数等于 8 的类型之一。假设设置中有诸如 ulong 的数字。由于 double 类型不能保证准确再现较大的 ulong 值,因此需要使用联合体将 ulong“打包”到 double 中,然后再“解包”回来。

#define MAX_LONG_IN_DOUBLE       9007199254740992
// FYI: ULONG_MAX            18446744073709551615
 
union ulong2double
{
   ulong U;   // 8 bytes
   double D;  // 8 bytes
};
ulong2double converter;
 
void OnStart()
{
   Print(sizeof(ulong2double)); // 8
   
   const ulong value = MAX_LONG_IN_DOUBLE + 1;
   
   double d = value// possible loss of data due to type conversion
   ulong result = d// possible loss of data due to type conversion
   
   Print(d" / "value" -> "result);
   // 9007199254740992.0 / 9007199254740993 -> 9007199254740992
   
   converter.U = value;
   double r = converter.D;
   Print(r);               // 4.450147717014403e-308
   Print(offsetof(ulong2doubleU), " "offsetof(ulong2doubleD)); // 0 0
}

ulong2double 结构体的大小等于 8,因为它的两个字段都具有此大小。因此,字段 U 和 D 完全重叠。

就整数而言,9007199254740992 是 double 类型能够可靠存储的最大精确整数值。在本例中,我们尝试用 double 来多存储一个数字。

ulongdouble 的标准转换会造成精度损失:将 9007199254740993 写入 double 类型的变量 d 后,我们读取到的值是经过“舍入”的值 9007199254740992(有关以 double 类型存储数字的更多细节,请参见 实数一节)。

使用转换器时,数字 9007199254740993 将“原样”地写入联合体,无需转换,因为我们将其赋值给 ulong 类型的 U 字段。该数字的 double 表示可从字段 D 中获得,同样没有经过转换。我们可以放心地将其复制到其他变量和数组(如 double)。

虽然生成的 double 值看起来很奇怪,但如果需要通过反向转换提取,它与原始整数完全匹配:写入 double 类型的 D 字段,然后从 ulong 类型的 U 字段读取。

联合体可以包含构造函数和析构函数,也可以包含方法。默认情况下,联合体成员具体公共访问权限,不过可以像在结构体中那样,使用访问修饰符进行调整。