c语言进阶 自定义类型 枚举,联合
自定义类型 枚举 联合
- 枚举
- 1. 枚举类型的定义
- 2. 枚举的优点
- 2. 枚举的优点
- 3. 枚举的使用
- 枚举的实际应用场景
- 枚举的实际应用场景
- 联合
- 1. 联合类型的声明、定义与大小计算
- 2. 联合的特点
- 3. 联合大小的计算规则
- 联合的实际应用场景
- 联合的实际应用场景
- 枚举与联合的区别
枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
1. 枚举类型的定义
- 枚举是一种用于定义命名常量集合的自定义类型,使用
enum
关键字声明。它将一组具有逻辑关联的离散值整合在一起,每个值都有一个有意义的名称。 - 示例:
// 定义表示星期的枚举类型enum Day
enum Day
{Mon, // 第一个枚举常量,默认值为0Tues, // 第二个枚举常量,值为前一个常量值+1,即1Wed, // 值为2Thur, // 值为3Fri, // 值为4Sat, // 值为5Sun // 值为6
};
- 枚举常量的值规则:
- 若未显式赋值,第一个枚举常量默认值为 0,后续常量的值依次在前一个基础上加 1。
- 可以在定义时为枚举常量显式赋值,打破默认递增规则。显式赋值后,后续未赋值的常量仍按前一个常量值加 1 的规则取值。
显式赋值示例:
enum Color
{RED = 1, // 显式赋值为1GREEN, // 前一个常量值为1,故该值为2BLUE = 4, // 显式赋值为4YELLOW // 前一个常量值为4,故该值为5
};
此时,RED
的值为 1,GREEN
的值为 2,BLUE
的值为 4,YELLOW
的值为 5。
2. 枚举的优点
2. 枚举的优点
- 与
#define
定义常量相比,枚举具有以下显著优点:
- 增强代码可读性和可维护性:
#define
定义的常量是孤立的,如#define MON 0
、#define TUES 1
,仅从常量名难以直观看出它们之间的关联。- 枚举将相关常量组合在一个类型中,如
enum Day
中的Mon
、Tues
等,清晰地表明它们都属于星期这一类别,代码含义更明确。当需要修改常量值时,只需在枚举定义中修改,无需在代码中逐个查找#define
的位置,维护更便捷。
- 具备类型检查,更严谨:
#define
定义的常量在预处理阶段会被直接替换为对应的值,没有类型概念。例如#define RED 1
,在代码中int a = RED
是合法的,char b = RED
也合法,编译器不做类型检查。- 枚举常量属于特定的枚举类型,枚举变量只能接收该枚举类型的常量(虽然C语言允许隐式转换,但编译器可能会给出警告)。例如:
enum Color { RED, GREEN, BLUE };
enum Color clr;
clr = RED; // 合法,类型匹配
clr = 1; // 不推荐,编译器可能警告(int类型赋值给enum Color类型)
3. 避免命名污染:
#define
定义的常量作用域是全局的,若在不同地方定义同名常量,会导致命名冲突。例如:
#define MAX 100
// 在另一个地方
#define MAX 200 // 编译错误,重复定义
- 枚举常量的作用域局限于枚举类型内部,不同枚举类型中可以有同名常量,不会冲突。例如:
enum A { MAX = 100 };
enum B { MAX = 200 }; // 合法,两个MAX属于不同枚举类型
- 便于调试:
调试时,枚举常量会以其名称显示,而#define定义的常量会显示为替换后的数值。例如调试包含enum Day { Mon, Tues }
的代码时,变量值为Mon
会显示Mon
,而#define MON 0
会显示0
,使用枚举更易追踪变量含义。 - 一次定义多个相关常量,使用便捷:
定义一组相关常量时,#define需要逐个定义,如:
#define RED 1
#define GREEN 2
#define BLUE 3
枚举只需在{}
中列出所有常量,一次定义完成,语法更简洁:
enum Color { RED = 1, GREEN = 2, BLUE = 3 };
3. 枚举的使用
- 枚举类型定义后,需定义枚举变量来使用,且应使用枚举常量为枚举变量赋值,以保证类型的一致性和代码的规范性。
- 示例:
enum Color
{RED = 1,GREEN = 2,BLUE = 4
};
// 定义枚举变量clr,并使用枚举常量GREEN赋值
enum Color clr = GREEN;
注意事项:
- 虽然 C 语言允许将整数直接赋值给枚举变量(如
clr = 5
),因为枚举在底层是按整数存储的,但这种做法破坏了枚举的类型特性,可能导致代码逻辑混乱。例如5并非enum Color
中定义的常量,将其赋值给clr后,后续判断clr的值时可能出现非预期结果。 - 规范的用法是仅使用枚举类型中定义的常量为枚举变量赋值,确保枚举变量的值始终在预期的范围内。
枚举的实际应用场景
枚举的实际应用场景
- 枚举在实际开发中常用于表示固定的状态、选项等。
- 表示状态:
- 示例:在游戏开发中,用枚举表示游戏角色的状态。
enum PlayerState
{IDLE, // idle状态,角色静止RUNNING, // running状态,角色奔跑JUMPING, // jumping状态,角色跳跃ATTACKING// attacking状态,角色攻击
};
// 定义角色状态变量
enum PlayerState state = IDLE;
// 根据状态执行不同操作
if (state == RUNNING)
{// 执行奔跑相关逻辑
}
这样的代码清晰明了,看到state == RUNNING就知道是角色处于奔跑状态。
- 表示选项:
- 示例:在配置相关的代码中,用枚举表示不同的配置选项。
enum ConfigOption
{LOW, // 低配置MEDIUM,// 中配置HIGH // 高配置
};
// 选择配置
enum ConfigOption option = MEDIUM;
相较于用0
、1
、2
表示配置,枚举让代码的意图更明确。
联合
1. 联合类型的声明、定义与大小计算
- 联合(共用体)是一种特殊的自定义类型,其所有成员共享同一块内存空间。
- 联合类型的声明:
// 声明联合类型union Un,包含char和int两种类型的成员
union Un
{char c; // char类型成员,占1字节int i; // int类型成员,通常占4字节(不同系统可能有差异)
};
- 联合变量的定义:
// 定义union Un类型的变量un
union Un un;
- 联合大小的计算:
- 联合的大小至少是其最大成员的大小,因为要能容纳最大的成员。
- 对于
union Un
,char c
占 1 字节,int i
占 4 字节,最大成员大小为 4 字节,所以sizeof(un)
的结果为4
。 - 示例代码及输出:
printf("%d\n", sizeof(un)); // 输出结果为4
2. 联合的特点
- 联合最核心的特点是所有成员共享同一块内存空间,这意味着:
- 联合变量的地址和其各成员的地址相同。
- 修改一个成员的值,会覆盖其他成员在该内存空间中的值,即修改一个成员会影响其他成员。
- 示例:
union Un
{int i; // 占4字节char c; // 占1字节,与i共享内存
}u;// 给成员i赋值0x11223344(假设系统为小端存储,低地址存放低字节)
u.i = 0x11223344;
// 此时内存中存储的字节从低到高为:0x44、0x33、0x22、0x11// 给成员c赋值0x00,c占用的是内存的低地址部分
u.c = 0x00;
// 此时内存中低地址的字节被修改为0x00,内存存储变为:0x00、0x33、0x22、0x11
// 成员i的值也随之变为0x11223300
结论:由于联合成员共享内存,同一时刻只能有效使用一个成员,修改一个成员会改变其他成员的值,这是联合与结构体的本质区别(结构体成员各自占用独立的内存空间)。
3. 联合大小的计算规则
- 联合大小的计算需遵循以下规则:
- 联合的大小至少是其最大成员的大小,以保证能容纳最大的成员。
- 当最大成员的大小不是联合中最大对齐数的整数倍时,联合的大小需要向上调整为最大对齐数的整数倍。
- 对齐数:对于联合中的每个成员,其对齐数为该成员类型的对齐数(通常默认对齐数为类型自身的大小,如char对齐数为1,short为2,int为4等,不同编译器可能有细微差异),联合的最大对齐数为所有成员对齐数中的最大值。
- 示例1:计算union Un1的大小
union Un1
{char c[5]; // char类型对齐数为1,数组占5字节int i; // int类型对齐数为4,占4字节
};
- 最大成员大小为
5
字节(char c [5]
)。 - 联合的最大对齐数为
4
(int
类型的对齐数)。 5
不是4
的整数倍,向上调整为8
(4 的 2 倍),所以sizeof(union Un1) = 8
。
输出示例:
printf("%d\n", sizeof(union Un1)); // 输出8
示例 2:计算 union Un2
的大小
union Un2
{short c[7]; // short类型对齐数为2,数组占14字节(2*7)int i; // int类型对齐数为4,占4字节
};
最大成员大小为 14
字节(short c [7]
)。
联合的最大对齐数为 4
(int 类型的对齐数)。
14
不是 4
的整数倍,向上调整为 16
(4 的 4 倍),所以sizeof(union Un2) = 16
。
输出示例:
printf("%d\n", sizeof(union Un2)); // 输出16
联合的实际应用场景
联合的实际应用场景
- 联合由于成员共享内存的特性,在一些特定场景下非常有用。
- 节省内存:
- 当需要存储不同类型的数据,但这些数据不会同时使用时,用联合可以节省内存。
- 示例:一个数据结构,有时需要存储整数,有时需要存储字符,用联合比结构体更节省空间。
// 用联合
union Data
{int num;char ch;
};
// 用结构体
struct DataStruct
{int num;char ch;
};
// 比较大小
printf("联合大小:%d\n", sizeof(union Data)); // 输出4
printf("结构体大小:%d\n", sizeof(struct DataStruct)); // 输出8(假设int占4字节,char占1字节,考虑对齐)
可以看到,联合比结构体更节省内存。
- 判断系统大小端:
- 大小端是指数据在内存中的存储顺序,大端是高字节存低地址,小端是低字节存低地址。
- 示例:用联合判断系统是大端还是小端。
union EndianCheck
{int i;char c;
};
union EndianCheck ec;
ec.i = 1;
// 如果是小端,1的低字节存低地址,c的值为1;如果是大端,c的值为0
if (ec.c == 1)
{printf("小端系统\n");
}
else
{printf("大端系统\n");
}
- 利用联合成员共享内存的特点,通过c的值可以判断系统的大小端。
枚举与联合的区别
- 枚举和联合都是C语言中的自定义类型,但它们有明显的区别:
特点 | 枚举 | 联合 |
---|---|---|
内存管理 | 成员是独立的常量,不共享内存 | 成员共享同一块内存 |
用途 | 表示固定的离散值集合 | 节省内存、处理不同类型数据但不同时使用的场景 |
大小计算 | 枚举类型变量大小通常与int相同 | 至少是最大成员的大小,且是最大对齐数的整数倍 |
成员关系 | 成员是不同的常量,相互独立 | 成员共享内存,修改一个影响其他 |