文章目录
- 1.P66 类型双关
- 1.1.实例1
- 1.2.实例2
- 2.P67 联合体union
- 2.1.实例1
- 2.2.实例2
- 2.2.1.使用类型双关(指针类型重解释)
- 2.2.2.使用联合体union
1.P66 类型双关
参考:视频 笔记
类型双关用来在C++中绕过类型系统。C++中有类型系统,当我们创建变量时必须声明整数或双精度数或类等等。C++中的类型是由编译器强制执行的,但我们可以直接访问内存。所以实际上内存双关就是我们对某个类型的内存进行另一种类型的解释,因为我们直接拿到了内存地址,可以把它解释为任意数据类型,可以对它进行任意操作。从另一个角度看,类型双关实际上就是C语言中的指针类型转换操作,是原始的内存操作。
1.1.实例1
把int当作double来看待。我们取a的地址然后把它转换成double指针,然后用解引用从指针回到实际类型。这就是类型双关,从int到double。但是注意这样访问这个doule值是错误的,因为int是4字节,double有8个字节,剩下的4个字节是未初始化的内存。
#include<iostream>
#include<vector>int main()
{int a = 50;double value = *(double*)&a;std::cout << value << std::endl;std::cin.get();
}
若不想创建全新的变量,只是想把int当作double来访问,只需要在double后面加上&,引用而不是拷贝成一个新的变量。这样就会编辑int内存,若决定在这里写0,实际上要写8个字节而不是4个字节。但我们只有4个字节的空间,可能会导致程序崩溃。
double& value = *(double*)&a;
value = 0.0;
1.2.实例2
若有一个结构体包含两个整数变量,在内存中结构体就是由两个整数组成的。结构体本身不包含任何类型的填充,任何类型的数据。如果结构体是空则至少要有一个字节,用来存储如何找到这个结构体。如果结构体里面有数据,那么结构体大小就是数据大小。
如下程序,这里可以将Entity结构体看成是一个int数组,而且不用通过e.x,e.y就可以提取出这些整数。我们创建一个原始数组int* position,让它等于e的地址,然后将e的地址转换成int*,然后打印出来。因为我们将它们转换成了数组,所以像访问数组那样访问它们。e包含了int数组的开始地址,也就是包含了指向int的指针。
#include<iostream>struct Entity
{int x, y;
};int main()
{Entity e = { 5,8 };int* position = (int*)&e;std::cout << position[0] << "," << position[1] << std::endl;std::cin.get();
}
通常我们会写int y=e.y;,但也可以&e取e的内存地址,然后将其转换成char*,因为是一个字节的大小,所以要向前移动四个字节,然后把这个内存转换成一个int指针,然后再解引用。我们打印这短疯狂的代码,按下F5得到了8,这就是e的y位置。
//int y=e.y;
int y = *(int*)((char*)&e + 4);
2.P67 联合体union
参考:视频 笔记
联合体写法上有点像类类型,或者结构体类型。但是它内部如果有多个成员变量的话,这些成员变量会公用同一个内存。比如用联合体声明声明4个浮点数float a, b, c, d
,那么这四个变量会占用同一个内存,所以联合体的大小是4个字节,而非16个字节。但是如果一个结构体声明了4个浮点数float a, b, c, d
,则这个结构体占16个字节内存。我们可以给联合体添加静态函数或者普通函数或者方法等等,但不能使用虚方法。
2.1.实例1
通常使用联合体是和类型双关紧密相关的,当想给 一个变量取两个不同的名字(比如有一个Vector3 XYZ,还有一个Vector3 RGB,使用union括住他们俩的话就可以同时使用XYZ和RGB访问同一个变量) 时非常有用。通常union是 匿名使用(即不需要给这个union命名为union myUnion之类的) 的,但是匿名union不能含有成员函数。如下代码。
#include<iostream>int main()
{// 定义了一个结构体,这个就是结构体,和union无关struct Union {// 这里面用union括起来,说明里面的float a 和int b占用相同的内存。否则他们俩就是占用不同的内存union{float a;int b;};};Union u;u.a = 2.0f;std::cout << u.a << "," << u.b << std::endl;std::cin.get();
}
运行代码得到2和107xxxx,因为107xxxx是浮点数形式的2字节表示,就好像取了组成浮点数的内存,然后把它解释成一个模型,这就是类型双关了。
2.2.实例2
2.2.1.使用类型双关(指针类型重解释)
如下代码有Vector2,Vector4
两个结构体,还有一个函数PrintVector2
,参数类型是Vector2
。
通过观察可以发现实际上Vector4
就是由两个Vecotr2
组成的。所以我访问Vecotr4
的时候为了提高访问效率,可以一次访问两个数,即把他们分成x,y
和z,w
两组来访问,每一组都可以看成是一个Vecotr2
的类型。
所以结构体的成员函数GetA()
进行类型双关,把x
强制解释成一个Vecotor2
类型的数据,这样就可以一次访问到x,y
两个变量了。首先获得x
的地址,然后类型双关强制解释为Vecotor2*
,最后指针解引用得到变量值,然后返回Vecotr2
的引用,这样不会复制而是指向x,y
。GetB()
同理
#include<iostream>struct Vector2
{float x, y;
};struct Vector4
{float x, y, z, w;Vector2& GetA(){return *(Vector2*)&x;}Vector2& GetB(){return *(Vector2*)&z;}
};void PrintVector2(const Vector2& vector)
{std::cout << vector.x << "," << vector.y << std::endl;
}int main()
{std::cin.get();
}
2.2.2.使用联合体union
另外一个方法是在Vector4
中定义一个union
联合体结构,然后它包括Vector
原来的数据float x,y,z,w
。
由于我们目的是让我们的数据float x, y, z, w
能转换为两个Vecotr
的格式进行访问,所以还需要在联合体中定义新的数据Vecotr a, b
。
注意:由于union
里面的变量会只会占有同一个内存,所以不能直接把float x, y, z, w
和Vecotr a, b
写在union
里面,这样就变成6个变量共享内存了。我们想要的是两个变量共享内存,所以使用结构体struct
把他们包起来。注意这里的结构体是匿名结构体,也就是直接写struct
,而没有名字struct A
之类的。因为这里就是想组织一下数据结构而已。
#include<iostream>struct Vector2
{float x, y;
};struct Vector4
{union{// 若写成struct A,即给结构体取了名字A,则会报错。因为他这里应该是匿名的// 如果它是匿名的,它只是一种数据结构,没有添加任何东西。// 这里使用struct组织成结构体的好处是它将4个变量转换成1个单元,这就是union所期望做的事。struct{// 真正的变量在这里float x, y, z, w;};// 再建立一个结构体,这样这个结构体里面的变量和上面的结构体里面的变量就共享内存了struct{Vector2 a, b;};};
};void PrintVector2(const Vector2& vector)
{std::cout << vector.x << "," << vector.y << std::endl;
}
现在有几种访问Vector4
内数据的方法,可以用xywz
或者ab
,a
和xy
的内存是一样的,b
和zw
的内存一样。如下代码
int main()
{Vector4 vector = { 1.0f,2.0f,3.0f,4.0f };PrintVector2(vector.a);PrintVector2(vector.b);vector.z = 500.0f;std::cout << "------------------" << std::endl;PrintVector2(vector.a);PrintVector2(vector.b);vector.x = 2.0f;std::cin.get();
}