结构体、联合与枚举组织复杂数据一、本篇文章要解决什么问题前面你学过的数据类型都是单个的一个 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 的定义和使用#includestdio.h#includestring.hstructStudent{intid;// 学号charname[20];// 姓名doublescore;// 成绩};intmain(void){structStudentstu1;// 定义一个结构体变量stu1.id1001;strcpy(stu1.name,Zhang);stu1.score95.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(inti0;i3;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*pstu;// 两种访问方式等价printf(%d\n,(*p).id);// 先解引用再取成员——括号必须有printf(%d\n,p-id);// 箭头运算符——更简洁.和-的区别.用于结构体变量-用于结构体指针。p-member等价于(*p).member。初学者常把这两个搞混——看到指针就用-看到变量就用.。图15-3 . 和 - 运算符的使用场景图让初学者记住变量用点、指针用箭头。3.4 联合 union——同一块空间的不同解读#includestdio.hunionData{inti;doubled;charc;};intmain(void){unionData u;u.i65;printf(u.i %d (占用 %u 字节)\n,u.i,(unsignedint)sizeof(u.i));u.cA;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——给整数起别名#includestdio.henumColor{RED,GREEN,BLUE};// RED0, GREEN1, BLUE2enumStatus{OK200,NOT_FOUND404,ERROR500};intmain(void){enumColorcRED;printf(RED %d\n,c);// 输出 0enumStatussNOT_FOUND;printf(NOT_FOUND %d\n,s);// 输出 404return0;}如果不手动指定值枚举从 0 开始递增。也可以手动赋任意值。四、完整代码示例一个学生信息管理的基础版本用结构体数组存储数据用函数指针选择操作#define_CRT_SECURE_NO_WARNINGS#includestdio.h#includestring.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(inti0;icount;i){printf([%d] ,i1);printStudent(arr[i]);}}intmain(void){structStudentstudents[MAX_STUDENTS]{{1001,Zhang,95.5},{1002,Li,88.0},{1003,Wang,76.5}};intcount3;intchoice;do{printf(\n1. 显示所有学生\n);printf(2. 添加学生\n);printf(0. 退出\n);printf(请选择);scanf(%d,choice);switch(choice){case1:printAll(students,count);break;case2:if(countMAX_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*pstu;p.name// 错误p 是指针用 - 或 (*p).namep-name// 正确错误3用 比较两个结构体structStudents1{...},s2{...};if(s1s2){...}// 编译错误结构体不能直接用 比较// 需要逐个成员比较错误4union 同时使用多个成员unionData u;u.i65;u.cA;// u.i 的值现在无效了不要同时依赖两个成员错误5enum 的值被当成字符串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 { MON1, TUE, WED, THU, FRI, SAT, SUN }。用户输入 1~7 的数字程序输出对应的星期名称。九、本篇总结struct 把不同类型数据打包作为一个整体使用。访问成员用.变量或-指针结构体数组适合批量管理相同结构的数据如一个班的学生union 成员共享同一块内存大小等于最大成员同时只能用其中一个enum 给整数常量起好记的名字提高代码可读性函数传结构体建议用指针避免大量数据复制