1、联合体(union)
1.1 什么是联合体
联合体是一种特殊的数据类型,它类似于结构体,联合体也是由一个或者多个成员构成这些成员可以是不同的类型。
但是在联合体中,编译器只为最大的成员分配足够的内存空间。这使得联合体与结构体有一个明显的区别,联合体中所有成员共用同一块内存空间。所以联合体也叫:共用体。
1.2 联合体的特点
因为联合体的这个特点,我们可以在某些场景下使用联合体节省空间,下面来看一下例子:
#include <stdio.h>
struct S//结构体
{
char ch;//0
//1 2 3
int 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];//5
int 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定义的标识符相比枚举有类型检查,更加严谨
- 使用方便,一次可以定义多个常量
- 枚举常量遵循作用域规则的,枚举声明在函数内,只能在函数内使用