1、联合体(union)
1.1 什么是联合体
联合体是一种特殊的数据类型,它类似于结构体,联合体也是由一个或者多个成员构成这些成员可以是不同的类型。
但是在联合体中,编译器只为最大的成员分配足够的内存空间。这使得联合体与结构体有一个明显的区别,联合体中所有成员共用同一块内存空间。所以联合体也叫:共用体。
1.2 联合体的特点
因为联合体的这个特点,我们可以在某些场景下使用联合体节省空间,下面来看一下例子:
#include <stdio.h>struct S//结构体{char ch;//0 //1 2 3int i;//4 8 4 //4 5 6 7 };union U//联合体{char ch;int i;};int main(){printf("S=%d\n", sizeof(struct S));//打印结构体所占字节数printf("U=%d\n", sizeof(union U));//打印联合体所占字节数return 0;}
不知道大家是否还记得结构体中大小是怎么计算的了,这里也复习一下
首先字符类型ch从0开始偏移,整型类型 i自身大小是4,与默认对齐数8比较,i对齐数为4,因此1,2,3内存会被浪费,从4开始,4,5,6,7用来存储i的大小,因此总偏移量从0~7,占8个字节。所以结构体S的大小为8。
联合体中,ch占一个字节,i占4个字节,结果打印总共占4个字节。
是不是很神奇,为什么联合体只占了4个字节的空间呢?
其实前面在介绍联合体的时候就已经说过了,在联合体中,编译器只为最大的成员分配足够的内存空间。在U中,字符类型的ch占1个字节,整型类型的 i 占4个字节,所以编译器只会为整型i分配4个内存的空间。
到这里我们大致清楚联合体的这个特点有什么作用。但是联合体究竟是怎么共用同一块空间的呢?
union U{char ch;int i;};int main(){printf("U=%d\n", sizeof(union U));union U u;printf("%p\n", &u);printf("%p\n", &(u.ch));printf("%p\n", &(u.i));return 0;}
上图结果中我们可以看到无论是u还是u中的ch,u中的i,它们的地址都是一样的。
我们还可以换一种方式验证一下联合体中的成员都共用同一块空间。
union U{char ch;int i;};int main(){union U u;u.i = 0x11223344;u.ch = 0;return 0;}
这里给 i 和 ch 分别赋值,我们来监视一下
先F10调试,打开内存监视窗口
找u的地址,然后开始调试
这里将 i 的值放到内存中了,i已经将4个字节占满了,接下来继续调试,
可以发现第一个字节被改为了ch的值。
也就是说,在联合体中,如果我们改变其中一个成员赋值,其它成员的值也会跟着变化。
要注意不要把结构体的思想带到联合体中,在联合体中,如果你打算使用ch的值,就不要使用i,使用i的时候就不要使用ch。一次只使用联合体中的一个成员。
1.3 联合体的使用场景
需要创建多个成员,但是这些成员又不会被同时使用,且想要节省空间,就可以使用联合体来办。
如果每个成员都需要被赋值,需要同时使用,那就不能创建为联合体。
1.4 联合体大小的计算
关于联合体大小,只需要遵循两点即可
首先找到最大成员的大小,如果是最大对齐数的整数倍,那么最大成员的大小就是联合体的大小如果最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍联合体的大小至少是最大成员的大小。
举个例子:
union U{char ch[5];//5int i;//4};int main(){printf("%zd\n", sizeof(union U));return 0;}
上例中,联合体中最大成员的大小是5个字节,vs中默认最大对齐数是8,5不是8的整数倍,因此最终大小为8.
2、枚举(enum)
2.1 枚举类型的声明
说到枚举,想必大家都不觉得陌生,相比起联合体,枚举这个名词我们在很多地方都听说过,它在数学中又名穷举法。
还是简单介绍一下吧!
枚举顾名思义就是一一列举。
把可能的取值一一列举。
在现实生活中又一些枚举例子:
一周的星期一到星期日是有限的7天,可以一一列举一年的月份有12个月,也可以一一列举性别有男,女,保密,也可以一一列举向这样的数据就可以使用枚举了。
那么计算机中的枚举该怎么表示呢?
enum Sex{ //枚举类型的可能取值men,//常量women,secret};
上面enum是枚举关键字,Sex是枚举类型的名称,大括号中的便是枚举的成员,也都是常量,它们也被称为枚举常量。常量与常量之间用','隔开。
那么这些枚举常量的可能取值是多少呢?
#include <stdio.h>enum Sex{men,women,secret};int main(){printf("%d\n", men);printf("%d\n", women);printf("%d\n", secret);return 0;}
打印结果:
从这里我们可以看到枚举常量的可能取值是从0开始向下递增的,如果还有第四个常量,便会出现3……
枚举中它们为什么是常量呢?因为它们的值不能修改。如果修改它们的值会怎么样?
可以看到直接报错,不能修改。
虽然不能修改,但是我们可以给它们赋初始值,比如说
enum Sex{men=1,women=4,secret=8};
如果我们只给第一个常量赋初始值,会怎么样呢?
它会沿着第一个值向后递增。
如果我们不给第一个赋值,给第二个赋值,会怎么样呢?
可以看到第一个值还是默认的0,但是第三个值会沿着第二个赋的值继续向后递增。
2.2 枚举类型的优点
我们可以使用#define定义常量,为什么非要使用枚举呢?这不是多次一举吗?
但是事实并非如此,枚举还是有很多好处的。
枚举的优点:
增加代码的可读性和可维护性和#define定义的标识符相比枚举有类型检查,更加严谨使用方便,一次可以定义多个常量枚举常量遵循作用域规则的,枚举声明在函数内,只能在函数内使用