15_结构体联合与枚举_组织复杂数据
结构体、联合与枚举:组织复杂数据
一、本篇文章要解决什么问题
前面你学过的数据类型都是"单个"的:一个 int、一个 double、一个 char。但现实中很多数据是"打包"在一起的——比如一个学生的信息包括学号(int)、姓名(char 数组)、成绩(double),你不能用三个独立的变量随便放着,因为"张三的学号"和"李四的成绩"之间没有关联。
这篇文章帮你搞定三件事:
- struct 怎么把不同类型的数据打包成一个整体
- 怎么通过
.和->访问结构体成员 - union 和 enum 分别在什么场景下用
二、先用一个简单例子理解
2.1 档案袋的故事
你去学校教务处,每个学生有一个档案袋,袋子上写着名字,里面装着:学号条、成绩单、体检表。不同的材料类型不同(数字、文字、表格),但都装在一个袋子里,形成一个整体。
这个"档案袋"就是结构体(struct)——把不同类型的数据打包在一起,起一个名字,当作一个整体来传递。
2.2 union 就是"一物多用"的格子
一个储物柜的格子,今天放书包,明天放篮球。同一块空间,不同时间可以存不同类型的东西——这就是联合(union)。
2.3 enum 就是"给数字起个好记的名字"
红灯=0、绿灯=1、黄灯=2——这就是枚举(enum)。用RED比用0更可读。
三、核心知识点讲解
3.1 struct 的定义和使用
#include<stdio.h>#include<string.h>structStudent{intid;// 学号charname[20];// 姓名doublescore;// 成绩};intmain(void){structStudentstu1;// 定义一个结构体变量stu1.id=1001;strcpy(stu1.name,"Zhang");stu1.score=95.5;printf("学号:%d\n",stu1.id);printf("姓名:%s\n",stu1.name);printf("成绩:%.1f\n",stu1.score);return0;}运行结果:
学号:1001 姓名:Zhang 成绩:95.5定义时直接初始化:
structStudentstu2={1002,"Li",88.0};图15-1 结构体内存布局图:帮助理解结构体成员在内存中的排列。
3.2 结构体数组——一个班的学生
structStudentclass[3]={{1001,"Zhang",95.5},{1002,"Li",88.0},{1003,"Wang",76.5}};for(inti=0;i<3;i++){printf("%d %s %.1f\n",class[i].id,class[i].name,class[i].score);}图15-2 结构体数组示意图:帮读者理解结构体数组的连续存储。
3.3 结构体指针——用 -> 访问成员
structStudentstu={1001,"Tom",90.0};structStudent*p=&stu;// 两种访问方式等价:printf("%d\n",(*p).id);// 先解引用再取成员——括号必须有!printf("%d\n",p->id);// 箭头运算符——更简洁.和->的区别:.用于结构体变量,->用于结构体指针。p->member等价于(*p).member。初学者常把这两个搞混——看到指针就用->,看到变量就用.。
图15-3 . 和 -> 运算符的使用场景图:让初学者记住"变量用点、指针用箭头"。
3.4 联合 union——同一块空间的不同解读
#include<stdio.h>unionData{inti;doubled;charc;};intmain(void){unionData u;u.i=65;printf("u.i = %d (占用 %u 字节)\n",u.i,(unsignedint)sizeof(u.i));u.c='A';printf("u.c = %c\n",u.c);// 注意:此时 u.i 的值已经被覆盖了——因为 i 和 c 共用同一块空间printf("整个 union 大小:%u 字节\n",(unsignedint)sizeof(u));// sizeof(union Data) 等于最大成员的大小return0;}union 所有成员共享同一块内存空间,union 的大小等于其最大成员的大小。同一时间只能使用一个成员——你写入一个成员,其他成员的值就被覆盖了。
图15-4 union 和 struct 内存占用对比图:一目了然地展示 union 的 “共享空间” 特性。
3.5 枚举 enum——给整数起别名
#include<stdio.h>enumColor{RED,GREEN,BLUE};// RED=0, GREEN=1, BLUE=2enumStatus{OK=200,NOT_FOUND=404,ERROR=500};intmain(void){enumColorc=RED;printf("RED = %d\n",c);// 输出 0enumStatuss=NOT_FOUND;printf("NOT_FOUND = %d\n",s);// 输出 404return0;}如果不手动指定值,枚举从 0 开始递增。也可以手动赋任意值。
四、完整代码示例
一个"学生信息管理"的基础版本,用结构体数组存储数据,用函数指针选择操作:
#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>#defineMAX_STUDENTS50#defineNAME_LEN30structStudent{intid;charname[NAME_LEN];doublescore;};// 打印单个学生voidprintStudent(conststructStudent*s){printf("学号:%d 姓名:%-10s 成绩:%.1f\n",s->id,s->name,s->score);}// 打印所有学生voidprintAll(conststructStudentarr[],intcount){printf("\n===== 学生列表(共 %d 人)=====\n",count);for(inti=0;i<count;i++){printf("[%d] ",i+1);printStudent(&arr[i]);}}intmain(void){structStudentstudents[MAX_STUDENTS]={{1001,"Zhang",95.5},{1002,"Li",88.0},{1003,"Wang",76.5}};intcount=3;intchoice;do{printf("\n1. 显示所有学生\n");printf("2. 添加学生\n");printf("0. 退出\n");printf("请选择:");scanf("%d",&choice);switch(choice){case1:printAll(students,count);break;case2:if(count>=MAX_STUDENTS){printf("学生已满!\n");break;}printf("请输入学号、姓名、成绩:");scanf("%d %19s %lf",&students[count].id,students[count].name,&students[count].score);count++;printf("添加成功!\n");// 注意:%s 遇到空格就停,所以姓名不能含空格。// 如需支持"Zhang San"这类姓名,应改用 fgets 读取整行break;case0:printf("再见!\n");break;default:printf("无效选择\n");}}while(choice!=0);return0;}五、运行结果
1. 显示所有学生 2. 添加学生 0. 退出 请选择:1 ===== 学生列表(共 3 人)===== [1] 学号:1001 姓名:Zhang 成绩:95.5 [2] 学号:1002 姓名:Li 成绩:88.0 [3] 学号:1003 姓名:Wang 成绩:76.5 1. 显示所有学生 2. 添加学生 0. 退出 请选择:2 请输入学号、姓名、成绩:1004 Zhao 82.0 添加成功! 1. 显示所有学生 2. 添加学生 0. 退出 请选择:0 再见!六、代码逐行解析
结构体作为函数参数——传指针而非传值:
voidprintStudent(conststructStudent*s)为什么不直接传struct Student s?因为结构体可能很大(如果有很多字段),值传递会完整复制一份——既慢又浪费内存。传指针只复制 4 或 8 字节的地址。const保证函数不会修改原始数据。
结构体数组成员访问:
students[count].idstudents[count]是数组中的一个结构体变量,.id访问它的成员。如果要写成指针形式:(students + count)->id。
do while 菜单循环:
do{...}while(choice!=0);菜单至少显示一次,用户选 0 才退出——这是典型的 do while 场景。
七、初学者常见错误
错误1:忘记 struct 关键字(C 中不能省略)
// C 语言中structStudentstu;// 正确Student stu;// 错误(除非用了 typedef,见第 16 篇)错误2:结构体指针用 . 而不是 ->
structStudent*p=&stu;p.name// 错误!p 是指针,用 -> 或 (*p).namep->name// 正确错误3:用 == 比较两个结构体
structStudents1={...},s2={...};if(s1==s2){...}// 编译错误!结构体不能直接用 == 比较// 需要逐个成员比较错误4:union 同时使用多个成员
unionData u;u.i=65;u.c='A';// u.i 的值现在无效了!不要同时依赖两个成员错误5:enum 的值被当成字符串
enumColor{RED,GREEN};printf("%s\n",RED);// 错误!输出的是整数 0,不是 "RED"// 需要手动维护名称数组来转换八、练习题
练习题1:分数统计
定义结构体struct Score { double chinese; double math; double english; }。用户输入 3 科成绩,程序计算总分和平均分,存入结构体并输出。
练习题2:按成绩排序
在完整代码示例的学生数组中,按成绩从高到低排序并输出。用冒泡排序,交换整体结构体(结构体可以直接赋值:struct Student temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;)。
练习题3:用 enum 表示星期
定义一个enum Weekday { MON=1, TUE, WED, THU, FRI, SAT, SUN }。用户输入 1~7 的数字,程序输出对应的星期名称。
九、本篇总结
- struct 把不同类型数据打包,作为一个整体使用。访问成员用
.(变量)或->(指针) - 结构体数组适合批量管理相同结构的数据(如一个班的学生)
- union 成员共享同一块内存,大小等于最大成员;同时只能用其中一个
- enum 给整数常量起好记的名字,提高代码可读性
- 函数传结构体建议用指针,避免大量数据复制
