前面我们用结构体把不同数据打包在一起一个struct Student里同时有姓名、学号、成绩各占各的空间互不干扰。但有时候我们需要的恰恰相反同一个存储空间在不同时刻存放不同类型的数据。比如一个变量有时存整数有时存浮点数但从来不同时存在或者我们要用一种紧凑的方式去“拆解”一个整数的各个字节。这时候结构体的“孪生兄弟”——共用体union就登场了。同时我们还会认识一个让代码更优雅的工具枚举enum。它能把一堆有关系的整数常量组织起来让代码的可读性上一个台阶。一、共用体是什么共用体union是一种特殊的复合类型它所有的成员共享同一块内存空间。同一时刻只有一个成员是“活跃”的修改一个成员会覆盖其他成员的值。定义共用体的语法和结构体几乎一样只是把struct换成unionunionData{inti;doubled;charc;};union Data的大小不是三个成员大小之和而是最大成员的大小加上可能的对齐填充。因为所有成员都用同一个地址存不同的类型时就是“变脸”。#includestdio.hunionData{inti;doubled;charc;};intmain(void){unionData u;printf(sizeof(union Data) %zu\n,sizeof(u));// 通常是 8double 大小u.i42;printf(u.i %d\n,u.i);// 42printf(u.d %f\n,u.d);// 未定义行为输出垃圾值printf(u.c %c\n,u.c);// 未定义行为u.d3.14;printf(u.i %d\n,u.i);// 未定义行为被覆盖了printf(u.d %f\n,u.d);// 3.14return0;}要点同一时刻共用体只能保存一个成员的值。读取一个非最后一次写入的成员结果是未定义行为虽然多数实现只是把位模式重新解释。共用体的大小等于其最宽成员的大小。共用体与结构体的核心区别结构体struct共用体union成员存储各自独立同时存在共享同一空间大小≥ 所有成员大小之和对齐后≥ 最大成员大小同时有效成员数全部一个典型用途打包不同类型数据类型多态、节省内存、拆解数据二、共用体的典型应用场景1. 节省内存多种类型不同时使用比如你正在开发一个绘图程序图形属性里有一个“填充色”但填充色可能是 RGB 值三个整数也可能是灰度值一个整数。它们不会同时使用。structShape{inttype;// 0灰度, 1RGBunion{intgray;// 灰度值 0~255struct{intr,g,b;}rgb;// RGB 三色}color;};intmain(void){structShapes;s.type1;s.color.rgb.r255;s.color.rgb.g128;s.color.rgb.b64;// 现在 s.color.gray 是无意义的return0;}2. 判断大小端字节序大小端Endianness是指多字节数据在内存中的存储顺序。大端模式将高字节存低地址小端模式反之。利用共用体可以轻易检测当前平台#includestdio.hintis_little_endian(void){union{inti;charc[sizeof(int)];}u;u.i1;returnu.c[0]1;// 小端低字节1在最低地址}intmain(void){if(is_little_endian()){printf(小端模式\n);}else{printf(大端模式\n);}return0;}写入int值为 1四字节01 00 00 00 或 00 00 00 01然后读第一个字节若是 1说明低地址存低字节即小端。3. 拆解数据按类型和按字节访问网络编程、协议解析中常需要一个 32 位整数但又要能单独操作每个字节。共用体非常方便unionIPAddress{uint32_taddr;uint8_tbytes[4];};intmain(void){unionIPAddress ip;ip.bytes[0]192;ip.bytes[1]168;ip.bytes[2]1;ip.bytes[3]100;printf(IP: %u.%u.%u.%u\n,ip.bytes[0],ip.bytes[1],ip.bytes[2],ip.bytes[3]);printf(作为 32 位整数: 0x%08X\n,ip.addr);return0;}注意此类用法依赖平台的字节序不同平台结果可能不同。三、枚举给整数常量起个有意义的名字有时程序中需要一组相关的整数常量比如星期1~7、颜色红黄蓝绿、状态码成功、失败、超时。直接用数字写满代码可读性差又容易出错intcolor1;// 1 是红色还是蓝色得翻文档枚举enum就是为这个场景设计的。它定义一组命名整数常量让代码自解释。enumWeekday{MONDAY1,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY};规则如果不手动赋值枚举值默认从 0 开始依次递增。可以手动指定某个值后续值自动递增MONDAY 1则TUESDAY自动为 2。多个枚举常量可以有相同的值但不推荐。声明枚举变量enumWeekdaytodayWEDNESDAY;if(todaySATURDAY||todaySUNDAY){printf(周末愉快\n);}C 语言中枚举类型底层是int所以枚举变量可以和整数混用虽然编译器可能不警告但为了类型清晰应该避免把裸整数赋给枚举变量。四、枚举与switch是好搭档枚举配合switch非常自然某些编译器如 GCC/Clang在开启特定警告选项时可以检测 switch 是否覆盖了所有枚举值。#includestdio.henumDirection{NORTH,SOUTH,EAST,WEST};voidmove(enumDirectiondir){switch(dir){caseNORTH:printf(向北走\n);break;caseSOUTH:printf(向南走\n);break;caseEAST:printf(向东走\n);break;caseWEST:printf(向西走\n);break;default:printf(未知方向\n);break;}}五、枚举 vs 宏为什么优先用枚举用#define也能定义常量#defineRED0#defineGREEN1#defineBLUE2但枚举有不可替代的优势#define宏enum枚举类型检查无纯文本替换有枚举是独立类型调试信息调试器只能看到数字可以看到枚举常量名作用域宏全局有效除非#undef受作用域控制可放在结构体/函数内自增赋值需手动指定自动递增因此能用枚举的地方尽量不要用宏定义一堆零散的整数常量。六、常见错误与陷阱1. 读取共用体非活跃成员unionData u;u.i10;printf(%f\n,u.d);// 未定义行为值是垃圾严格来说这是 UB尽管常被用于类型双关。若确实需要类型双关C99 起可以使用 union 进行类型双关在 GCC/Clang 等编译器中是支持的扩展但不是严格标准行为更安全的做法是使用 memcpy。。2. 枚举值当成字符串enumColor{RED,GREEN,BLUE};printf(%s\n,RED);// 错误打印出数字或崩溃枚举值是整数不能直接当字符串输出。如果需要在调试中输出名称通常手工写转换函数。3. 枚举类型混用导致意外赋值enumColor{RED0,GREEN1,BLUE2};enumDirection{NORTH0,SOUTH1};enumColorcNORTH;// 编译可能不报错但逻辑上是错的虽然都是int但不同枚举类型混用会让代码混乱。尽量保持类型一致。4. 对共用体使用sizeof误当成成员之和unionU{inta;doubleb;};printf(%zu\n,sizeof(unionU));// 通常是 8不是 12共用体大小只需容下最大的那个。七、小结今天你认识了结构体的两个“亲戚”共用体让多个成员共享同一块内存用于节省空间、检查字节序、协议解析等。使用时务必清楚当前活跃的成员是谁。枚举给整数常量赋予了有意义的名字让代码更可读、更安全。配合switch使用优雅高效。共用体与结构体有时会联合使用比如前面看到的带标记的Shapetype字段指示当前共用体的含义这其实是 C 语言实现“变体记录”或“tagged union”的经典手法。现在你已经可以自由组合结构体、共用体、枚举来构建复杂的数据模型。但数据只在程序运行时存在——一旦程序退出变量就消失了。怎么把数据长久保存下一篇我们进入文件操作的世界fopen、fclose、fprintf、fscanf让你的程序能读写磁盘上的文件真正“留下痕迹”。课后小练习定义一个共用体Number包含int、float、double三个成员。写一个函数print_number(union Number n, int type)根据 type 的值打印对应的成员。在main中测试。利用共用体写一个函数输入一个unsigned int交换它的高低 16 位并返回例如0x12345678变成0x56781234。提示用共用体配合unsigned short数组。定义一个枚举HttpStatus包含常见的 HTTP 状态码200, 301, 404, 500 等并手动指定值。写一个函数get_status_message(enum HttpStatus code)返回对应的字符串描述。小挑战设计一个“配置文件解析器”的数据模型配置项的值可能是整数、浮点数或字符串。用共用体和枚举结合实现一个ConfigValue类型并编写设置和打印函数根据类型打印不同格式的值。我们下期见