一、枚举
枚举 是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。是一个被命名的整型常数的集合。简单来说就将某种特定类型的对象一一进行列举,一一列举特定类型可能的取值。
顾名思义就是一 一列举,把可能的取值一 一列举。
比如在我们的日常生活中:
每周的星期一到星期日都是有限的7天,可以一一列举
性别有:男、女,也可以一一列举。
月份有12个月,也可以一一列举
说白了,枚举常量属于枚举类型,值是整形。
1.1 枚举的定义
枚举的定义与结构体类似
1. enum 枚举类型名 2. { 3. 标识符1, 4. 标识符2, 5. … 6. };
- 枚举类型名受自己定义,如:week,year…,标识符就是其中的枚举常量,如Mon,Tues,Wed…
- 每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
1.2 枚举的使用
1.2.1 枚举的声明
如果我们想对一个星期,不用枚举用#define:
#define MON 1 #define TUE 2 #define WED 3 #define THU 4 #define FRI 5 #define SAT 6 #define SUN 7
使用枚举简化,代码示例如下:
enum DAY { MON=1, //指定从1开始,否则默认从0开始 TUE, WED, THU, FRI, SAT, SUN };
与结构体类似,我们也可以使用typedef来简化枚举
typedef enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN }DAY;
1.2.2 打印枚举常量
typedef enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN }DAY; int main() { for (int i = MON; i < SUN; i++) { printf("%d ", i); } return 0; }
输出结果如下:
0 1 2 3 4 5 6
这也间接证明枚举是一个常量,默认从0开始,后面依次递增1。
1. 这些枚举类型的可能取值也叫做枚举常量,那既然是常量,就意味这不能被修改。
typedef enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN }DAY; int main() { MON = 9; for (int i = MON; i < SUN; i++) { printf("%d ", i); } return 0; }
2. 虽然不能修改,但是我们可以在定义是给它们赋初值。
如果只给第一个赋初值,就从该初值开始,还是依次增1。
typedef enum DAY { MON = 3, TUE, WED, THU, FRI, SAT, SUN }DAY; int main() { for (int i = MON; i < SUN; i++) { printf("%d ", i); } return 0; }
输出结果为:
3 4 5 6 7 8 9
如果中间某个赋初值,前面的还是默认值,后面的会依次增1。
typedef enum DAY { MON, TUE, WED, THU = 6, FRI, SAT, SUN }DAY; int main() { for (int i = MON; i < SUN; i++) { printf("%d ", i); } return 0; }
输出结果为:
0 1 2 6 7 8 9
1.2.3 枚举变量的创建与初始化
那定义好了类型,我们就可以那这些类型来定义变量了:
typedef enum Color//颜色 { RED, GREEN, BLUE }Color; int main() { Color col = RED; }
1.3 枚举的大小
已给枚举变量,只会用来存放一个枚举常量的值,所以一个枚举变量的大小应该就是一个 int 的大小,也就是4个字节。下面我们通过代码来测试一下。
#include <stdio.h> enum color1 { RED, GREEN, BLUE }; enum color2 { GRAY = 0x112233445566, YELLOW, PURPLE }; int main() { printf("%d\n", sizeof(enum color1)); printf("%d\n", sizeof(enum color2)); return 0; }
输出结果为:
4
4
1.4 枚举的优点
于是这里就产生了一个问题,枚举类型的成员均为常量,不可在使用中被修改,那么我们同样可使用宏 #define 去定义常量,为什么非要使用枚举类型呢?
这是因为,相比于宏,枚举类型具有很多优点:
- 增加代码的可读性和可维护性
- 和 #define 定义的标识符比较,枚举有类型检查,更加严谨
- 防止了命名污染(封装)
- 便于调试,#define在调试的时候会完成替换
- 使用方便,一次可以定义多个变量
二、联合体
2.1 联合体的定义
和结构体一样,联合也是一种特殊的自定义类型,这种类型定义的变量也包含有一系列的成员,但不同的是这些成员共用同一块空间(遂也被称作共用体)
union UN { char c; int i; }; //定义了一个共用体类型 int main() { union UN un;//定义了一个共用体变量 printf("%d\n", sizeof(un)); return 0; }
2.2 联合体的特点
那现在大家来思考一个问题,上面的联合体变量un的大小是多少?
结果为4
这是为什么呢,在这里要注意上面的一句话:这些成员公用同一块空间(所以联合也叫共用体)。
联合体un只有两个成员,char c; int i;c 大小1个字节,i是最大的成员4个字节。
那么因为它们共用同一块空间,所以四个字节就够了。
4个字节既可以放得下c ,也能放得下i。
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员),受限于联合体的特点,我们不会同时使用联合里面的成员,只会用一种的一个。
我们要怎么证明这一点?
union UN { char c; int i; }; //定义了一个共用体类型 int main() { union UN un;//定义了一个共用体变量 printf("%d\n", sizeof(un)); printf("%p\n", &un); printf("%p\n", &un.c); printf("%p\n", &un.i); return 0; }
输出结果为:
4
00EFFA2C
00EFFA2C
00EFFA2C
可以看出, un 、 un.c 、 un.i 它们三个的起始地址都一样,这也说明,联合体的成员确实会共用同一块内存空间。
那他们共用一块内存空间意味着什么呢?
是不是意味着它们不能同时存在啊,或者说它们不能同时使用。
就拿上面的哪个union Un来说:当我们使用成员 i 时,对于c来说,此时c的那一个字节里面是不是存的是 i 的内容,就相当于此时c是不存在的。
让我们来看一段代码:
1. un.i = 0x11223344; 2. un.c = 0x55; 3. printf("%x\n", un.i);
因为它们共用一块空间,上述代码中先给un.i赋值。
那此时un的四个字节的空间里放的应该是这样的内容:
那我们在去给un.c赋值,是不是就会改变1个字节的内容(因为un.c是1个字节)。
应该就变成这样了:
那我们再以printf("%x\n", un.i);
(%x是以16进制形式打印)打印出来是不是就是11223355
了。
来验证一下:
输出结果为:
这样更好的验证了联合体的成员共用一块空间,不能同时存在。
2.3 联合体大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
来看下面这么一段代码:
#include<stdio.h> //联合体1: union TEST1 { char c[5]; int i; }; //联合体2: union TEST2 { short c[7]; int i; }; int main() { //检查联合体的大小: printf("The size of TEST1 is %d\n", sizeof(union TEST1)); printf("The size of TEST2 is %d\n", sizeof(union TEST2)); return 0; }
输出结果为:
The size of TEST1 is 8
The size of TEST2 is 16
在联合体 TEST1 中,占用空间最大的成员是 char 类型数组 c ,且其中含有 5 个元素,则其所占空间大小为 5 个字节,而我们都知道 VS 的对齐数默认为 8 ,则将会对齐至默认对齐数 4 的整数倍,即 8 个字节;而联合体 TEST2 中,占用空间最大的成员是 short 类型数组 c ,且其中含有 7 个元素,则其所占空间的大小为 14 个字节,那么就将会对齐至对齐数 4 的整数倍,即 16 个字节.
2.4 联合体判断机器大小端
我们早在学习数据在内存中的存储时就已经了解过一种判断大小端的方法,今天就为大家介绍另一种方法——通过联合体判断大小端。
那如何取出第一位呢?除了通过指针,我们也能利用联合体共用同一块内存这一性质判断。
代码如下:
int check_sys() { union { int i; char c; }un; un.i = 1; return un.c; //返回1是⼩端,返回0是⼤端 } int main() { int ret = check_sys(); if (ret == 1) { printf("⼩端\n"); } else { printf("⼤端\n"); } return 0; }