C语言中有3种自定义类型:结构体、枚举和联合体.
今天,我们讲述的是枚举和联合体。
1、枚举
A、枚举的定义
枚举是一种自定义的数据类型,可以在程序中创建新的类型,并为其赋一组明确的值
以下是枚举类型的定义:

如上图所示,我们定义了一个枚举类型,其中包含7个枚举常量,各个枚举常量间使用逗号分隔
考虑到声明该类型的变量时需使用类型 enum Day,过于繁琐,所以我们使用typedef关键字将该类型重命名为Day
B、枚举变量的声明和使用

我们在上述代码中,声明了一个枚举类型的变量d,同时将其赋值为Sun
实质上,d虽为枚举类型,但其实际值为1,所以以有符号整型的方式打印d,实际输出的就为1

当然,要理解这一点,需要对枚举常量进行一些分析。
C、枚举常量
定义枚举类型时,其花括号内包含的就是各个枚举常量,其值默认从0开始,而后1,以此类推。

当然,我们也可自己对枚举常量的值进行更改,也就是对枚举常量进行赋值操作

如上图所示,我们可以令Mon为1,则此时Tues为2,以此类推。这样我们就达到了对枚举常量的值更改的目的。
不过有一点需要注意,一旦对某个枚举常量进行赋值操作,该枚举常量前的枚举常量不会受到影响,而其之后的枚举常量都会相应改变。当然,也可以对多个枚举常量进行赋值操作:

我们可以直接使用枚举常量,即把枚举常量当成相应数字使用:

相应输出结果为星期一:
D、枚举常量与宏
通过以上介绍,我们可以发现,枚举定义枚举常量与宏(#define定义的标识符常量),二者在本质上其实差不多,那么枚举常量相较于宏而言,有何优势呢?
其一,使用枚举类型一次可以定义多个枚举常量,而宏只能一个一个定义
其二,使用枚举定义枚举类型便于调试,因为宏在调试前的预处理过程中,实质上会用相应的数值进行替换,因而在调试过程中,想要在监视窗口中观察相应的宏的值是无法做到的,而枚举常量则不会被替换,是可以在监视窗口中进行观察的,如下图所示:

2、联合体
A、联合体的定义
联合体,C语言中一种自定义的数据类型,其最大的特点在于联合体成员共用内存空间,因而联合体又被称为共用体
联合体定义时需要使用到关键字union:

我们可以看到,在定义上,联合体类型与结构体类型几乎相同
B、联合体变量的声明和使用

由于联合体中内存空间是共用的,因而联合体成员不能同时使用,所以上述联合体成员的赋值不能同时进行
C、联合体在内存中的存储
a、联合体所占内存大小
由于联合体成员是共用内存空间的,所以其内存空间的计算遵循以下两条规则:
1、联合体所占内存大小至少是联合体中最大成员所占内存大小
2、联合体存在内存对齐,该内存对齐确保联合体所占内存大小为各联合体成员中最大对齐数的整数倍
以下通过两个例子进行阐释:
例1

该联合体中最大成员的大小为4个字节,所以该联合体所占内存至少为4个字节,同时该联合体最大对齐数为4(对齐数的相关知识见博主的另一篇博客浅谈匿名结构体和结构体内存对齐),4个字节是4的整数倍,因此,该联合体的大小即为4个字节。

例2:

联合体的第一个成员为数组arr,占5个字节;第二个成员为整型类型,占4个字节。
因此,该联合体至少为5个字节,考虑最大对齐数为4,所以该联合体大小需扩充到8个字节
这里有两点需要注意:
1、数组的的对齐数为其成员的大小与VS默认对齐数中的最小值,而不是数组大小与VS默认对齐数中的最小值。如上述的arr数组,成员为char类型,大小为1,因而该数组的对齐数为1,而不是5
2、包含5个char类型变量的数组与单纯的5个char类型变量,在联合体中是有区别的。对于数组,会开辟5个字节的空间;而对于5个char类型的变量,则只会开辟1个字节的空间,然后这5个变量共用。

此时该联合体所占内存大小为4个字节(注意区分这两种联合体)
b、联合体的内存使用
由于联合体成员是共用内存的,因此一个联合体成员的改变会影响到其它联合体成员。
我们看下面这段代码:

调试起来,看内存中的存储情况:

调试到此处时,内存中的存储情况:

将51行的语句执行完后,内存中的存储情况:

可见低地址处的44被11覆盖了,此时对s.a的赋值影响到了内存中存储的s.c的值。
此时,我们打开监视窗口,观察两者的值:

由此可见,联合体成员共用内存的特性致使联合体成员间是相互影响的。
D、利用联合体判断大小端字节序存储
我们可以通过联合体来判断数据在内存中的存储顺序

如果是小端字节序存储:
由低地址向高地址,对s.c赋值后,联合体s在内存中存储的是 11 00 00 00 ,在对s.a赋值后,联合体s在内存的中存储变为 00 00 00 00。
所以如果是小端字节序存储,则输出结果应为0.
如果是大端字节序存储:
由低地址向高地址,对s.c赋值后,联合体s在内存中存储的是 00 00 00 11,在对s.a赋值后,联合体s在内存中的存储依旧是 00 00 00 11。
所以,如果是大端字节序存储,则输出结果应为17。
(大小端字节序存储的相关知识见博主的另一篇博客:浅谈整型类数据在内存中的存储)

而最终的输出结果为0,说明在VS2022的编译环境下是小端字节序存储。