news 2026/7/4 4:23:31

23. 【C语言】共用体与枚举类型:当数据需要“变脸”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
23. 【C语言】共用体与枚举类型:当数据需要“变脸”

前面我们用结构体把不同数据打包在一起,一个struct Student里同时有姓名、学号、成绩,各占各的空间,互不干扰。

但有时候,我们需要的恰恰相反:同一个存储空间,在不同时刻存放不同类型的数据。比如一个变量有时存整数,有时存浮点数,但从来不同时存在;或者我们要用一种紧凑的方式去“拆解”一个整数的各个字节。这时候,结构体的“孪生兄弟”——共用体(union)就登场了。

同时,我们还会认识一个让代码更优雅的工具:枚举(enum)。它能把一堆有关系的整数常量组织起来,让代码的可读性上一个台阶。


一、共用体是什么?

共用体(union)是一种特殊的复合类型,它所有的成员共享同一块内存空间。同一时刻,只有一个成员是“活跃”的,修改一个成员会覆盖其他成员的值。

定义共用体的语法和结构体几乎一样,只是把struct换成union

unionData{inti;doubled;charc;};

union Data的大小不是三个成员大小之和,而是最大成员的大小(加上可能的对齐填充)。因为所有成员都用同一个地址,存不同的类型时就是“变脸”。

#include<stdio.h>unionData{inti;doubled;charc;};intmain(void){unionData u;printf("sizeof(union Data) = %zu\n",sizeof(u));// 通常是 8(double 大小)u.i=42;printf("u.i = %d\n",u.i);// 42printf("u.d = %f\n",u.d);// 未定义行为,输出垃圾值printf("u.c = %c\n",u.c);// 未定义行为u.d=3.14;printf("u.i = %d\n",u.i);// 未定义行为,被覆盖了printf("u.d = %f\n",u.d);// 3.14return0;}

要点:

  • 同一时刻,共用体只能保存一个成员的值。
  • 读取一个非最后一次写入的成员,结果是未定义行为(虽然多数实现只是把位模式重新解释)。
  • 共用体的大小等于其最宽成员的大小。

共用体与结构体的核心区别

结构体(struct)共用体(union)
成员存储各自独立,同时存在共享同一空间
大小≥ 所有成员大小之和(对齐后)≥ 最大成员大小
同时有效成员数全部一个
典型用途打包不同类型数据类型多态、节省内存、拆解数据

二、共用体的典型应用场景

1. 节省内存:多种类型不同时使用

比如你正在开发一个绘图程序,图形属性里有一个“填充色”,但填充色可能是 RGB 值(三个整数),也可能是灰度值(一个整数)。它们不会同时使用。

structShape{inttype;// 0=灰度, 1=RGBunion{intgray;// 灰度值 0~255struct{intr,g,b;}rgb;// RGB 三色}color;};intmain(void){structShapes;s.type=1;s.color.rgb.r=255;s.color.rgb.g=128;s.color.rgb.b=64;// 现在 s.color.gray 是无意义的return0;}

2. 判断大小端(字节序)

大小端(Endianness)是指多字节数据在内存中的存储顺序。大端模式将高字节存低地址,小端模式反之。利用共用体可以轻易检测当前平台:

#include<stdio.h>intis_little_endian(void){union{inti;charc[sizeof(int)];}u;u.i=1;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)、颜色(红黄蓝绿)、状态码(成功、失败、超时)。直接用数字写满代码,可读性差又容易出错:

intcolor=1;// 1 是红色?还是蓝色?得翻文档

枚举(enum)就是为这个场景设计的。它定义一组命名整数常量,让代码自解释。

enumWeekday{MONDAY=1,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY};

规则:

  • 如果不手动赋值,枚举值默认从 0 开始,依次递增。
  • 可以手动指定某个值,后续值自动递增(MONDAY = 1,则TUESDAY自动为 2)。
  • 多个枚举常量可以有相同的值(但不推荐)。

声明枚举变量:

enumWeekdaytoday=WEDNESDAY;if(today==SATURDAY||today==SUNDAY){printf("周末愉快!\n");}

C 语言中,枚举类型底层是int,所以枚举变量可以和整数混用(虽然编译器可能不警告),但为了类型清晰,应该避免把裸整数赋给枚举变量。


四、枚举与switch是好搭档

枚举配合switch非常自然,某些编译器(如 GCC/Clang)在开启特定警告选项时,可以检测 switch 是否覆盖了所有枚举值。

#include<stdio.h>enumDirection{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

但枚举有不可替代的优势:

#defineenum枚举
类型检查无(纯文本替换)有,枚举是独立类型
调试信息调试器只能看到数字可以看到枚举常量名
作用域宏全局有效(除非#undef受作用域控制,可放在结构体/函数内
自增赋值需手动指定自动递增

因此,能用枚举的地方,尽量不要用宏定义一堆零散的整数常量。


六、常见错误与陷阱

1. 读取共用体非活跃成员

unionData u;u.i=10;printf("%f\n",u.d);// 未定义行为!值是垃圾

严格来说这是 UB,尽管常被用于类型双关。若确实需要类型双关,C99 起可以使用 union 进行类型双关(在 GCC/Clang 等编译器中是支持的扩展,但不是严格标准行为),更安全的做法是使用 memcpy。。

2. 枚举值当成字符串

enumColor{RED,GREEN,BLUE};printf("%s\n",RED);// 错误!打印出数字或崩溃

枚举值是整数,不能直接当字符串输出。如果需要在调试中输出名称,通常手工写转换函数。

3. 枚举类型混用导致意外赋值

enumColor{RED=0,GREEN=1,BLUE=2};enumDirection{NORTH=0,SOUTH=1};enumColorc=NORTH;// 编译可能不报错,但逻辑上是错的

虽然都是int,但不同枚举类型混用会让代码混乱。尽量保持类型一致。

4. 对共用体使用sizeof误当成成员之和

unionU{inta;doubleb;};printf("%zu\n",sizeof(unionU));// 通常是 8,不是 12

共用体大小只需容下最大的那个。


七、小结

今天你认识了结构体的两个“亲戚”:

  • 共用体让多个成员共享同一块内存,用于节省空间、检查字节序、协议解析等。使用时务必清楚当前活跃的成员是谁。
  • 枚举给整数常量赋予了有意义的名字,让代码更可读、更安全。配合switch使用优雅高效。

共用体与结构体有时会联合使用,比如前面看到的带标记的Shapetype字段指示当前共用体的含义),这其实是 C 语言实现“变体记录”或“tagged union”的经典手法。

现在,你已经可以自由组合结构体、共用体、枚举来构建复杂的数据模型。但数据只在程序运行时存在——一旦程序退出,变量就消失了。怎么把数据长久保存?下一篇,我们进入文件操作的世界:fopenfclosefprintffscanf,让你的程序能读写磁盘上的文件,真正“留下痕迹”。


课后小练习

  1. 定义一个共用体Number,包含intfloatdouble三个成员。写一个函数print_number(union Number n, int type),根据 type 的值打印对应的成员。在main中测试。
  2. 利用共用体写一个函数,输入一个unsigned int,交换它的高低 16 位并返回(例如0x12345678变成0x56781234)。提示:用共用体配合unsigned short数组。
  3. 定义一个枚举HttpStatus,包含常见的 HTTP 状态码(200, 301, 404, 500 等),并手动指定值。写一个函数get_status_message(enum HttpStatus code),返回对应的字符串描述。
  4. (小挑战)设计一个“配置文件解析器”的数据模型:配置项的值可能是整数、浮点数或字符串。用共用体和枚举结合实现一个ConfigValue类型,并编写设置和打印函数,根据类型打印不同格式的值。

我们下期见!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 4:20:30

WWDC 最大赢家不是苹果,是一个你看不见的 AI 模式

WWDC 2026 最让我意外的不是 Siri 终于变聪明了&#xff0c;是苹果选的人。今年 1 月&#xff0c;苹果和谷歌联合宣布了一项多年 AI 合作协议&#xff1a;苹果每年向谷歌支付约 10 亿美元授权费&#xff0c;把 Gemini 模型作为 Siri AI 的底层引擎。WWDC 上这事正式落地——Sir…

作者头像 李华
网站建设 2026/7/4 4:19:40

Linux cpio命令详解:高效备份与性能优化指南

1. cpio命令概述与核心价值cpio作为Linux系统中经典的备份工具&#xff0c;其设计哲学体现了Unix"小而美"的理念。与常见的tar命令相比&#xff0c;cpio在处理大量小文件时具有明显的性能优势&#xff0c;这也是为什么许多Linux发行版的initramfs仍然采用cpio格式的原…

作者头像 李华
网站建设 2026/7/4 4:13:20

TVA对具身智能领域的核心技术支撑(17)

前沿技术介绍&#xff1a;AI智能体视觉&#xff08;TVA&#xff0c;Transformer-based Vision Agent&#xff09;是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术&#xff0c;属于“物理AI” 领域的一种全新技术形态&#xff0c;完成了从“虚拟世界”到“…

作者头像 李华
网站建设 2026/7/4 4:08:18

DVWA从入门到精通(七):Insecure CAPTCHA(不安全的验证码)

摘要&#xff1a;本文是《DVWA从入门到精通》系列的第七篇&#xff0c;带你全面掌握Insecure CAPTCHA&#xff08;不安全的验证码&#xff09;模块的攻防全流程。从CAPTCHA验证码的设计初衷出发&#xff0c;逐步讲解Low、Medium、High三个级别的逻辑漏洞与攻击手法&#xff0c;…

作者头像 李华