C++类型双关及使用示例
一.什么是类型双关:
维基百科对类型双关的定义是这样:类型双关是计算机科学的术语,指任何编程技术能颠覆或者绕过一门程序设计语言的类型系统,以达成在形式语言内部难以甚至不可能实现的效果。
这个概念可能有些不好理解,其实简单的说它就是可以绕过类型系统。
首先我们知道任何类型的数据在存储时都是二进制数据。而它代表的类型如int,char,double,自定义结构体或是自定义类都只是在以自己的规则解读这个二进制数据。二进制流00110000(换算成10进制就是48)。如果我们把它看成一个char类型的数据那它就是字符'0'。因为48对应的ascii为0。而把它看成int就是48。因此一个数据流用不同的解读可以得到不同的意义。
但是在C++中我们定义一个变量必须规定类型,即对于上面的数据我们必须进行int a=32;或者char a='0';。假如我们int a=48,那么我们将无法直接以char的形式输出。类型双关可以帮我们实现这个需求。
c/c++绕过类型系统相对简单,像java,c#等语言绕过类型系统会相对复杂。
二.类型双关的实现:
c++类型双关实现很简单,如以下代码
char a[4]={0,0,0,3};
int b=((int*)a)[0];
int c=*((int*)a);
其中a为a[0]的地址,int*代表将这个地址看成int型数据的地址,这样((int*)a)就是一个int型指针,这样b和c的赋值就可以看懂了。
这样我们就将a这个字符串变成了int并赋给b。不过此时应该注意系统是大端还是小端字节序,简单的说就是确定寄存器是左侧是高位还是右侧是高位。如果左侧是高位则此时b的值为3,反之则为3*2^24。想了解大小端字节序可以通过站内搜索,百度或谷歌的方式,有很多优秀的博文介绍。
类型双关不仅可以用于基本类型的转换,还可以用于结构体或类和普通类型的类型转换,如下:
struct node {
int a;
int b;
};
int main()
{
node y;
double x=((double*)(&y))[0];
return 0;
}
这样我们就将node以double形式并赋值给x。类也同理。
三.使用类型双关的注意事项:
之所以在本章就说明类型双关的注意事项是因为类型双关是因为类型双关使用应该慎之又慎。类型双关会受编译器影响。以第二节的例子,int与char互相转化时,有些系统对int的长度规定为4,有些为2也有些为8,这样会导致同样的代码换平台运行结果天差地别。而这只是最简单的转换。当进行复杂一些的转换,比如结构体和类,当其中的一个元素的长度发生变化便会影响全局。在更复杂的代码中会产生更多的问题。
下面章节的内容是为了加深对类型双关的理解,但是本文的类型双关用法在真正使用到项目时需要严谨的考虑跨平台,可移植等问题。
四.结构体中的类型双关:
本章的代码风格仅为方便理解类型双关。如果在正式项目中使用还需进行多方面考虑。
4.1 字节对齐简介:
在开始之前,我们需要先简单了解字节对齐。字节对齐概况下来就是当结构体的基本元素只能将自己长度的整数倍作为存储的首地址,同时结构体长度只能是最长基本类型长度的整数倍。例子如下:
struct node1
{
char a;//从第0字节开始
int b;//int长度为4,因此从第4字节开始。
char c;//只能从第8字节开始
//最长基本类型字节为4,9不是4的整数倍,所以长度为12。
}
struct node2
{
char a;//从第0字节开始
char b;//只能从第2字节开始
int c;//int长度为4,因此从第4字节开始。
//长度为8
}
到此应该简单的了解了字节对齐。字节对齐在站内,百度和谷歌都有很多优秀的文章来讲解,并且最好掌握这个知识点。不过理解后文内容只需大概知道字节对齐是什么就可以。
4.2 使用类型双关访问结构体数据:
我们先创建一个存储学生成绩的结构体,如下:
struct student
{
char name[10];
int chinese;//语文
int math;//数学
int english;//英语
int physics;//物理
int chemistry;//化学
int biology;//生物
}
它存储着学生的成绩,当导入成绩时需要如下输入:
student student1;
std::cin>>student1.chinese>>student1.math>>student1.english;
std::cin>>student1.physics>>student1.chemistry>>student1.biology;
实际上写这些代码会比较累,而且当我们需要存储的是大学成绩,我们可能需要写更多的代码来进行一个cin。因此我们会想到如果能把它当成数组就好了。然而这完全可以实现。如下
student student1;
for(int i=0;i<6;i++)
{
std::cin>>(&student1.chinese)[i];
}
这样即可实现。同样的,我们可以将结构体设置一个能够得到&student1.chinese的函数,如下:
struct student
{
char name[10];
int chinese;//语文
int math;//数学
int english;//英语
int physics;//物理
int chemistry;//化学
int biology;//生物
int *getArray()
{
return &chinese;
}
int *getArray1()
{
return (int*)this+3;//+3是因为字节对齐
}
}
int main()
{
student student1;
for(int i=0;i<6;i++)
{
std::cin>>student1.getArray[i];
}
for(int i=0;i<6;i++)
{
std::cout<<student1.getArray1[i];
}
return 0;
}
这样也可以实现。
4.3 使用类型双关初始化结构体:
当我们需要对结构体进行初始化赋值时,如果所有的值为0,我们可以使用类似4.2的方法赋0,但我们也可以这样
struct student
{
char name[10];
int chinese;//语文
int math;//数学
int english;//英语
int physics;//物理
int chemistry;//化学
int biology;//生物
student()
{
for(int i=0;i<sizeof(*this);i++)
{
//使用该代码时一定要注意结构的内容是否含有指针等敏感内容。
//由于正式项目中初始化的值不一定为0,因此尽量不要以此法初始化。
((char*)this)[i]=0;
}
}
int *getArray()
{
return &chinese;
}
}
仍然可以实现初始化。
五.类中的类型双关:
本章的代码风格仅为方便理解类型双关。如果在正式项目中使用还需进行多方面考虑。
5.1 使用类型双关来访问私有变量:
我们知道私有变量无法直接在类外访问,但是类型双关可以帮助我们在类外得到它。
首先我们设计一个类,如下:
class wClass
{
private:
double a;
int b;
int c;
public:
wClass()
{
a = 1.1;
b = 2;
c = 3;
}
};
int main()
{
wClass wa;
std::cout<<wa.a<<'\n';//会报错
return 0;
}
显然,主函数无法访问私有的变量a,但是我们可以使用如下方式
int main()
{
wClass wa;
std::cout<<*((double*)&wa)<<'/n';//得到a
std::cout<<((int*)&wa)[2]<<'/n';//得到b
std::cout<<((int*)&wa)[3]<<'/n';//得到c
return 0;
}
我们就可以访问到abc,同样我们也可以修改它们。
当类中元素过多时可能不好分辨位置,尤其是类含有多种基本类型且分布较杂时,我们可以建立一个含有一样元素但是元素都是公开的类,如下:
class wClass
{
private:
double a;
int b;
int c;
public:
wClass()
{
a = 1.1;
b = 2;
c = 3;
}
};
class wClass1
{
public:
double a;
int b;
int c;
};
int main()
{
wClass wa;
wClass1 pwa=(wClass1*)(&wa);
pwa->a=1.6;
return 0;
}
这样我们就可以如公有一般访问,修改类的私有元素。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)