Cherno C++系列笔记23——P66~P67 类型双关、联合体union

news/2024/11/28 17:35:44/

文章目录

  • 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,yz,w两组来访问,每一组都可以看成是一个Vecotr2的类型。

所以结构体的成员函数GetA()进行类型双关,把x强制解释成一个Vecotor2类型的数据,这样就可以一次访问到x,y两个变量了。首先获得x的地址,然后类型双关强制解释为Vecotor2*,最后指针解引用得到变量值,然后返回Vecotr2的引用,这样不会复制而是指向x,yGetB()同理

#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, wVecotr 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或者abaxy的内存是一样的,bzw的内存一样。如下代码

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();
}

在这里插入图片描述


http://www.ppmy.cn/news/243832.html

相关文章

干货 | PostgreSQL 中的多版本并发控制

大部分的数据库系统都使用锁定来进行并发控制&#xff0c;而 PostgreSQL 的做法就略有不同。它使用多版本模型&#xff08;也称为多版本并发控制&#xff0c;Multi-Version Concurrency Control&#xff0c;简称 MVCC&#xff09;来维持数据的一致性。因此&#xff0c;在查询数…

Redis进阶:缓存穿透|缓存击穿|缓存雪崩问题

Redis应用问题 1. 缓存穿透问题1.1 问题描述1.2 解决方案方法一&#xff1a;空值缓存方法二&#xff1a;设置可访问的名单&#xff08;白名单&#xff09;方法三&#xff1a;采用布隆过滤器方法四&#xff1a;进行实时监控 2. 缓存击穿问题2.1 问题描述2.2 解决方案方法一&…

Java单元测试学习(二)

Java单元测试学习&#xff08;二&#xff09; 使用测试框架JUnitMockito和单元测试覆盖率框架JaCoCo 目录结构 依赖—很好&#xff0c;这里又有个小插曲 打开页面查看覆盖率时一直显示0/0---->最后的解决方式是①添加了maven-surefire-plugin插件 <?xml version&quo…

Linux内核文件写入流程

文本代码基于Linux 5.15 。 当用户层调用write 去做写入&#xff0c; linux 内核里面是如何处理的&#xff1f; 本文以exfat 为例&#xff0c; 讨论这个流程 入口函数 write 系统调用的定义如下&#xff1a; fs/read_write.c ssize_t ksys_write(unsigned int fd, const ch…

亚信科技 HVV面试复盘

亚信科技 HVV面试复盘 1.想做研判还是监测2.去年的国护厂商是什么3.天眼常见的日志检索的命令4.客户部署了负载均衡,流量是先通过负载服务器再到天眼上面的,这种情况怎么溯源找到它的原始攻击IP呢5.如果天眼中的日志检索流量包中不带xff字段呢6.weblogic都有那些漏洞7.文件上…

IT知识百科:什么是跨站脚本(XSS)攻击?

跨站脚本&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一种常见的网络安全漏洞&#xff0c;攻击者利用该漏洞在受害者的网页中插入恶意脚本&#xff0c;从而能够获取用户的敏感信息、劫持会话或进行其他恶意活动。本文将详细介绍跨站脚本攻击的原理、类型、常…

eNSP路由器启动不了

eNSP路由器启动不了 解决方法&#xff1a;打开防火墙&#xff0c;把有关ensp的都打开&#xff0c;因为可能是你防火墙关了。 点击”允许程序或功能通过windows防火墙”&#xff0c;进入具体设置界面&#xff1a; 找到eNSP_VboxServer前面打了“√”的那项&#xff0c;然后将该…

小米无线路由器服务器用户名和密码忘了,小米路由器用户名和密码是什么

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 小米路由器管理员一般是没有初始密码的&#xff0c;如果需要在登录路由器的时候提示输入密码&#xff0c;可以看看路由器底部有无密码&#xff0c;如果有密码的话就会在路由器的底部…