• 联合体有点像结构体,不过一次只能占用一个成员的内存,即每个成员都共享同一块内存
    • 如果我们有一个结构体,我们在里面声明四个浮点数,那就意味着我们有4个4字节,即那个结构体共有16个字节
    • 联合体只能有一个成员,如果声明4个浮点数(float),如A、B、C、D,联合体的大小仍为4个字节。当尝试访问A或B或C或D时,他们实际上是同一块内存,即把A改成5,马上访问D时值也是5
  • Struct 会为每个成员分配独立空间,而 Union 里的所有成员起始地址相同。
  • 使用联合体的方式和使用结构体或类完全一样,也能添加静态函数、普通函数和方法 不能有虚方法;静态成员不占 Union 实例空间
  • 类型双关(Type Punning):用不同的名称或类型查看同一块原始内存。
    • 当要给同一个变量取两个不同名字时,可以用联合体
  • 通常联合体匿名使用、也不添加方法

联合体不能有虚方法的原因#

虚函数的工作原理依赖于虚函数表指针(vptr)。

  • Class/Struct 的做法:编译器会在对象内存的开头插入一个隐藏的指针(vptr),指向该类的虚函数表(vtable)。
  • Union 的冲突:Union 的所有成员都必须从偏移量 0 开始共享内存。
    • 如果 Union 有虚函数,那么它必须有一个 vptr。
    • 这个 vptr 会占用内存。由于 Union 成员共享空间,你的数据成员(比如 int 或 float)会和这个 vptr 重叠。
    • 一旦你给成员赋值,就会覆盖掉 vptr,导致程序在调用虚函数时崩溃;反之,虚函数机制也会破坏你的数据。

例子:类型双关#

#ifdef LY_EP67_
#include <iostream>
 

int main()
{
	struct Union
	{
		union
		{
			float a;
			int b;
		};
	};

	Union u;

	//在 C++ 中,如果你在一个 struct 或 class 内部定义了一个
	//_没有名字_的 union,编译器会把这个联合体的成员直接注
	//入(Injected)到外层结构体的作用域中。
	u.a = 2.0f;//0x00F6FE20  00 00 00 40
	//40 00 00 00 为 0100 0000 0000 0000 0000 0000 0000 0000
	//u.b:0表示正数,所以这个值为2^30次方,即 10,7374,1824 
	//也就是我们拿到了构成那个浮点数的内存,然后把它当做int来解释(
	//类型双关)
	std::cout << u.a << "," << u.b << std::endl;

	std::cin.get();
}
#endif

相对拙劣的做法#

#ifdef LY_EP67
#include <iostream>

struct Vector2
{
	float x, y;
};

struct Vector4
{
	float x, y, z, w;

	Vector2& GetA()
	{
		//把&x当成一个Vector2的地址来解释
		//*表示取值的时候连续取两个float的值来构成一个Vector2
		return *(Vector2*)&x;
	}

	//尝试把Vector4看成是两个Vector2
	//Vector2 GetA()
	//{
	//	//..
	//	//添加一些成员并返回,这里省略了
	//	return Vector2();
	//}
};

void PrintVector2(const Vector2& v)
{
	std::cout << "Vector2: (" << v.x << ", " << v.y << ")\n";
}

int main()
{
	std::cin.get();
}
#endif

利用union原理并巧妙使用它#

#ifdef LY_EP67
#include <iostream>

struct Vector2
{
	float x, y;
};

//最外层 struct Vector4:定义了这块内
// 存的总大小(4 x 4 = 16 字节)。
struct Vector4
{

	//这是一个“平行空间”。它(union)告诉编译器:“我内部定义的所有东西,都从同一个起始地址开始存放”。
	union
	{

		//union的大小,是它内部最大的成员的大小。在这个例子中,union的大小是16字节(4个float,每个4字节)。

		//它把这 16 字节切分成了四个 float
		//,并分别取名为 x, y, z, w。(这个struct
		//是联合体的第1个成员)
		struct
		{
			float x, y, z, w;
		};

		//(这个struct是联合体的第2个成员)
		struct
		{
			Vector2 a, b;
		};

	};
};

void PrintVector2(const Vector2& v)
{
	std::cout << "Vector2: (" << v.x << ", " << v.y << ")\n";
}

int main()
{
	Vector4 vector = { 1.0f, 2.0f, 3.0f, 4.0f };

	//可以用vector.x,vector.y,vector.z,vector.w来访问这四个float
	//或者用vector.a和vector.b来访问这两个Vector2
	//且vector.a.x和vector.a.y分别对应vector.x和vector.y,vector.b.x和vector.b.y分别对应vector.z和vector.w(
	//因为它们共享同一块内存,所以修改其中一个成员会影响到其他成员的值)
	vector.x = 2.0f;
	std::cout << "Vector4: (" << vector.x << ", " << vector.y << ", " << vector.z << ", " << vector.w << ")\n";// Vector4: (2, 2, 3, 4)

	//验证上述结论
	//这里修改了z却间接修改了vector.b
	vector.z = 500.0f;
	std::cout << "-----" << std::endl;

	PrintVector2(vector.a); // Vector2: (2, 2)
	PrintVector2(vector.b); // Vector2: (500, 4)
	std::cin.get();
}
#endif