在 C 语言中,枚举(enum) 和 结构体(struct) 是两种核心的自定义数据类型,用于解决 “单一基础类型无法满足复杂数据描述” 的问题,但二者的设计目标、用法和场景完全不同 —— 枚举专注于 “限定离散的合法值”,结构体专注于 “聚合不同类型的数据”。
一、枚举(enum):限定离散取值的类型
1. 核心定义
枚举是一种 “命名常量集合”,用于将一组逻辑相关的离散值(如状态、选项、类型)赋予有意义的名字,避免直接使用魔法数字(无意义的整数),提升代码可读性和可维护性。
本质:枚举的每个成员本质是整数常量(默认从 0 开始递增,可手动指定值),编译器会自动将枚举名映射为对应整数。
2. 语法格式
// 1. 基本定义(无 typedef,使用时需加 enum 关键字)
enum 枚举名 {
成员1, // 默认值 0
成员2, // 默认值 1(依次递增)
成员3 = 5,// 手动指定值 5,后续成员默认+1(成员4=6)
成员4
};
// 2. 常用:typedef 简化(定义后可直接用 别名 代替 enum 枚举名)
typedef enum {
成员1,
成员2,
...
} 枚举别名;
3. 典型示例(结合之前的 DRESULT)
枚举最常用的场景是 “状态码”“选项标识”,比如 FatFs 的 DRESULT 就是枚举:
// 状态码枚举(限定文件操作的合法结果)
typedef enum {
DR_OK = 0, // 成功(手动指定 0)
DR_NO_FILE, // 1(默认递增)
DR_NO_PATH, // 2
DR_WRITE_PROTECTED // 3
} DRESULT;
// 使用示例
DRESULT res = DR_OK; // 直接用枚举成员赋值
if (res == DR_NO_FILE) { // 逻辑判断更直观
printf("文件不存在\n");
}
再举一个 “性别选项” 的简单示例:
typedef enum {
MALE, // 0
FEMALE, // 1
OTHER // 2
} Gender;
// 用枚举限定变量取值,避免传入非法值(如 3、-1)
void print_gender(Gender g) {
switch(g) {
case MALE: printf("男性\n"); break;
case FEMALE: printf("女性\n"); break;
case OTHER: printf("其他\n"); break;
}
}
int main() {
Gender person = MALE;
print_gender(person); // 输出:男性
// print_gender(3); // 警告/错误:3 不是 Gender 的合法成员
return 0;
}
4. 关键特性
- 成员是 常量:不能修改(如
MALE = 5;是错误的); - 本质是整数:可隐式转换为
int(如printf("%d", MALE);输出 0),但不建议随意转换(破坏类型安全性); - 作用:限定变量的合法取值范围,让代码更易读、少出错。
二、结构体(struct):聚合不同类型数据的容器
1. 核心定义
结构体是一种 “数据聚合类型”,用于将多个 “不同类型的变量”(如 int、char、数组、指针,甚至其他结构体)打包成一个整体,描述一个 “复杂对象”(如一个人、一个文件、一个坐标)。
本质:将分散的相关数据整合,方便管理和传递(避免函数参数过多)。
2. 语法格式
// 1. 基本定义(无 typedef,使用时需加 struct 关键字)
struct 结构体名 {
类型1 成员1; // 成员可以是任意基础类型/自定义类型
类型2 成员2;
...
};
// 2. 常用:typedef 简化(定义后可直接用 别名 代替 struct 结构体名)
typedef struct {
类型1 成员1;
类型2 成员2;
} 结构体别名;
// 3. 带标签的 typedef(支持结构体自引用,如链表节点)
typedef struct Node {
int data; // 数据域
struct Node* next; // 指针域(自引用,指向同类型结构体)
} Node;
3. 典型示例(描述 “学生”“文件对象”)
示例 1:描述学生(包含学号、姓名、成绩)
// 定义结构体(typedef 简化,别名 Student)
typedef struct {
int id; // 学号(int 类型)
char name[20]; // 姓名(字符数组)
float score; // 成绩(float 类型)
} Student;
int main() {
// 1. 定义结构体变量并初始化
Student stu1 = {101, "张三", 95.5};
// 2. 访问结构体成员(用 . 操作符)
printf("学号:%d\n", stu1.id);
printf("姓名:%s\n", stu1.name);
stu1.score = 98.0; // 修改成员值
printf("成绩:%.1f\n", stu1.score);
// 3. 结构体指针(用 -> 操作符访问成员)
Student* p = &stu1;
printf("指针访问姓名:%s\n", p->name);
return 0;
}
示例 2:FatFs 中的文件对象(FIL 结构体,真实场景)
// FatFs 定义的 FIL 结构体(存储单个文件的状态)
typedef struct {
FATFS* fs; // 指向所属的文件系统(结构体指针)
WORD id; // 文件句柄ID(整数)
BYTE flag; // 文件状态标志(枚举本质,如只读/可写)
DWORD fptr; // 当前文件指针位置(32位整数)
DWORD fsize; // 文件大小(32位整数)
DWORD clust; // 当前簇号(文件系统相关)
DWORD sect; // 当前扇区号(底层存储相关)
// ... 其他成员
} FIL;
// 使用:定义文件对象,作为 API 参数
FIL file;
f_open(&file, "test.txt", FA_READ); // 传入结构体指针
4. 关键特性
- 成员是 变量:可通过
.(直接访问)或->(指针访问)修改值; - 成员类型可混合:支持
int、char、数组、指针、其他结构体(如FIL中的FATFS*); - 内存布局:成员在内存中连续存储(存在内存对齐,默认按最大成员类型对齐);
- 作用:描述复杂对象,整合相关数据,方便传递和管理(如函数参数传递一个结构体指针,而非多个零散变量)。
三、枚举 vs 结构体:核心区别(表格对比)
| 特性 | 枚举(enum) | 结构体(struct) |
|---|---|---|
| 核心目标 | 限定离散的合法值(状态、选项) | 聚合不同类型的数据(描述复杂对象) |
| 成员本质 | 整数常量(不可修改) | 变量(可修改,类型可混合) |
| 内存占用 | 单个整数大小(通常 4 字节) | 所有成员内存之和(含对齐开销) |
| 访问方式 | 直接使用成员名(如 DR_OK) | 通过 . 或 -> 访问成员(如 stu.id) |
| 典型场景 | 状态码、选项标识(如 DRESULT、性别) | 描述对象(如学生、文件、链表节点) |
| 语法关键字 | enum | struct |
一句话总结:
- 想限定变量的 “合法取值”(比如操作结果只能是成功 / 失败 / 未找到)→ 用枚举;
- 想整合多个相关数据(比如一个学生的学号、姓名、成绩)→ 用结构体。
四、常见误区
- 混淆
typedef用法:枚举的typedef enum { ... } 别名和结构体的typedef struct { ... } 别名只是 “简化类型名” 的语法,核心区别在enum和struct关键字,而非typedef; - 枚举成员赋值:枚举成员是常量,不能在定义后修改(如
DR_OK = 1;错误); - 结构体内存对齐:结构体成员不是紧密排列的(如
char + int可能占用 8 字节,而非 5 字节),需注意内存对齐规则(可通过#pragma pack(1)取消对齐); - 结构体指针访问:结构体指针必须用
->访问成员,不能用.(如p->id正确,p.id错误)。