当前位置: 首页 > news >正文

C语言入门指南:联合体与枚举

目录

引言

1. 联合体 (Union) —— “内存共享大师”

1.1 什么是联合体?—— 一个“精打细算”的内存管家

1.2 什么联合体的大小是4?—— 代码验证

1.3 共享内存意味着什么?—— “牵一发而动全身”

1.4 联合体 vs 结构体 —— “合租” vs “整租”

1.5 联合体有啥用?—— “精打细算”的实战案例

1.6 小练习:用联合体判断“大端机”还是“小端机”

2. 枚举 (Enum) —— “让代码会说话”的命名大师

2.1 什么是枚举?—— 给数字穿上“有意义的外衣”

2.2 枚举常量的值可以自定义

2.3 为什么用枚举?—— 它的五大优点

2.4 枚举怎么用?—— 实战演练

基本用法:定义、声明、赋值

一个更复杂的例子:游戏角色状态

关于“整数赋值”的小插曲

总结:

联合体 (Union):内存的“共享公寓”

枚举 (Enum):代码的“翻译官”


引言

如果你的C语言学到“自定义类型”这一块儿,特别是“联合体”和“枚举”,说明你的学习已经进入了一个非常关键的阶段!别担心,这些概念初看可能有点抽象,但它们其实非常实用,而且理解了之后,你会觉得“哇,原来程序还能这么写!”。

所以今天我们来聊点能让你们代码瞬间“高大上”起来的东西——联合体(Union)枚举(Enum)

很多初学者会觉得,我有intcharfloat这些基本类型,有struct结构体就够了,为啥还要搞个“联合体”和“枚举”?这俩玩意儿是干啥的?有啥用?

问得好!这正是我们今天要解决的核心问题。我会用最通俗的语言,配合生动的比喻和实际的例子,让你不仅知道它们“是什么”,更明白“为什么”要用它们,以及“怎么用”它们。

1. 联合体 (Union) —— “内存共享大师”

我们先从“联合体”开始。这个名字听起来就有点“联合”、“共同”的意思,没错,它的核心思想就是共享

1.1 什么是联合体?—— 一个“精打细算”的内存管家

想象一下,你是一个非常节俭的人,你的衣柜里只有一件衣服。这件衣服很神奇,它既可以是一件T恤(夏天穿),也可以是一件羽绒服(冬天穿),还可以是一件西装(面试穿)。

但是! 你一次只能把它当成其中一种衣服来穿。你不能同时穿着T恤、羽绒服和西装出门,对吧?你必须选择一种状态。

联合体(Union)在内存里扮演的就是这个“神奇衣服”的角色。

在C语言里,我们通常用struct(结构体)来把不同类型的数据打包在一起。比如,一个学生的信息:

struct Student {char name[20]; // 姓名int age;       // 年龄float score;   // 分数
};

当你创建一个struct Student变量时,计算机会为nameagescore这三个成员各自分配独立的内存空间。它们互不干扰,井水不犯河水。

而联合体则完全不同!

union Un {char c; // 一个字符,占1个字节int i;  // 一个整数,通常占4个字节
};

当你创建一个union Un变量时,计算机会怎么做呢?它不会傻乎乎地给ci都分配空间。它会说:“你们俩共用一块地盘吧!这块地盘的大小,就按你们中个头最大的那个来算。”

在这个例子里,int通常是4个字节,char是1个字节,所以计算机会分配4个字节的内存给这个联合体变量。这4个字节,既是c的地盘,也是i的地盘。它们是同一块物理内存

这就是联合体最核心的特点:所有成员共享同一块内存空间。

1.2 什么联合体的大小是4?—— 代码验证

第一个例子:

#include <stdio.h>union Un {char c;int i;
};int main() {union Un un = {0}; // 定义并初始化联合体变量printf("%d\n", sizeof(un)); // 打印它的大小return 0;
}

运行结果:

为什么不是1(char的大小),也不是5(1+4)?因为联合体只为最大的成员(int,4字节)分配空间。它必须保证,无论你存的是char还是int,这块内存都够用。

1.3 共享内存意味着什么?—— “牵一发而动全身”

因为所有成员共用同一块内存,所以你给任何一个成员赋值,都会影响到其他成员的值。这就像你把那件“神奇衣服”从T恤状态换成了羽绒服状态,它就不再是T恤了。

#include <stdio.h>union Un {char c;int i;
};int main() {union Un un;un.i = 0x11223344; // 给整数成员赋值一个十六进制数printf("赋值后 i 的值: %x\n", un.i); // 输出: 11223344un.c = 0x55; // 给字符成员赋值printf("修改 c 后 i 的值: %x\n", un.i); // 输出: 11223355return 0;
}

发生了什么?

  1. 我们先把un.i设置为0x11223344。在内存中,这4个字节从低到高(假设是小端机)存的是:44, 33, 22, 11
  2. 然后,我们修改un.c。因为cchar,它只占1个字节,而且它和i共享内存的起始位置。所以,修改c,实际上就是修改了i所占4个字节中的第一个字节
  3. 把第一个字节从44改成了55,所以整个int的值就从0x11223344变成了0x11223355

再看一个地址的例子,证明它们是“一家人”:

#include <stdio.h>union Un {char c;int i;
};int main() {union Un un;printf("un.i 的地址: %p\n", &(un.i));printf("un.c 的地址: %p\n", &(un.c));printf("un 的地址: %p\n", &un);return 0;
}

运行结果会发现,这三个地址完全一模一样!这铁证如山地说明了,un.iun.cun本身,指向的是内存中的同一个地方。

1.4 联合体 vs 结构体 —— “合租” vs “整租”

为了更直观地理解,我们对比一下联合体和结构体。

// 结构体:每个成员都有自己的“单间”
struct S {char c; // 1字节int i;  // 4字节
};
// sizeof(struct S) 通常是 8 字节 (因为内存对齐)// 联合体:所有成员“合租”一个“大房间”
union U {char c; // 1字节int i;  // 4字节
};
// sizeof(union U) 是 4 字节
  • 结构体 (Struct):就像你租了一套两居室的房子。char c住一个小房间(1平米),int i住一个大房间(4平米),中间可能还有过道(内存对齐填充)。你们互不打扰,但总房租(内存)比较高。
  • 联合体 (Union):就像你租了一个4平米的单间。char cint i都住在这个单间里。任何时候,这个房间里只能放c或者i的东西,不能同时放。虽然挤了点,但房租(内存)便宜多了!

1.5 联合体有啥用?—— “精打细算”的实战案例

讲了这么多,联合体到底能干啥?难道就是为了让我们写出让同事看不懂的“炫技”代码吗?当然不是!它的核心价值在于节省内存,尤其是在嵌入式开发或处理大量数据时,这一点至关重要。

讲义里给了一个非常经典的例子:礼品兑换系统

假设我们要设计一个系统,里面有三种礼品:图书杯子衬衫

每种礼品都有一些公共属性:库存量、价格、商品类型。

但也有一些专属属性

  • 图书:书名、作者、页数。
  • 杯子:设计图案。
  • 衬衫:设计图案、颜色、尺寸。

笨办法(只用结构体):

struct Gift {int stock; // 库存double price; // 价格int type; // 类型:0=图书, 1=杯子, 2=衬衫// 下面是所有礼品的属性大杂烩char book_title[20]; // 书名char book_author[20]; // 作者int book_pages; // 页数char mug_design[30]; // 杯子设计char shirt_design[30]; // 衬衫设计int shirt_color; // 颜色int shirt_size; // 尺寸
};

这个设计简单粗暴,但问题很大:极度浪费内存!

想象一下,当这个Gift变量代表一个杯子时,book_titlebook_authorbook_pagesshirt_colorshirt_size这些字段都是完全用不到的,但它们依然占据着内存空间!一个杯子白白浪费了几十个字节。

聪明办法(联合体闪亮登场):

struct Gift {int stock; // 库存double price; // 价格int type; // 类型// 关键来了!用联合体来存放“专属属性”union {struct { // 图书的专属属性char title[20];char author[20];int pages;} book;struct { // 杯子的专属属性char design[30];} mug;struct { // 衬衫的专属属性char design[30];int color;int size;} shirt;} info; // 我们把这个联合体叫做 info
};

这个设计的精妙之处在哪?

  • info是一个联合体,它包含了三个结构体:bookmugshirt
  • 这三个结构体中,最大的是shirt(假设char[30] + int + int约38字节),所以info只占用大约38字节的内存。
  • 当商品是图书时,我们只使用info.book部分,info.muginfo.shirt的内存虽然存在,但我们不去碰它,也不会造成逻辑错误。
  • 同理,商品是杯子时,只用info.mug;是衬衫时,只用info.shirt

效果: 无论商品是什么类型,struct Gift占用的总内存都大致相同,而且比“笨办法”小得多!因为我们不再为每种商品都预留所有可能用到的字段,而是“按需分配”,用联合体实现了内存的“动态共享”。

这就是联合体在实际项目中的巨大价值——在保证功能的前提下,最大限度地节省宝贵的内存资源。

1.6 小练习:用联合体判断“大端机”还是“小端机”

这是一个非常经典的面试题,也是联合体的一个巧妙应用。

什么是大端/小端?

这指的是计算机存储多字节数字时,字节的排列顺序。

  • 小端 (Little-Endian):低位字节存放在内存的低地址处。这是我们最常见的,比如Intel的CPU。
  • 大端 (Big-Endian):高位字节存放在内存的低地址处。比如一些网络协议、老式Mac电脑。

怎么用联合体判断?

#include <stdio.h>int check_sys() {union {int i;char c;} un;un.i = 1; // 给整数赋值1// 1的二进制是 00000000 00000000 00000000 00000001// 如果是小端机,最低地址存的是 00000001 (即1)// 如果是大端机,最低地址存的是 00000000 (即0)return un.c; // 返回第一个字节的值
}int main() {if (check_sys() == 1) {printf("恭喜!你的电脑是小端机。\n");} else {printf("你的电脑是大端机。\n");}return 0;
}

原理:

  1. 我们定义了一个联合体,包含一个int和一个char
  2. int赋值1。
  3. 读取char的值。因为charint共享内存的起始地址,所以char读到的就是int第一个字节
  4. 如果第一个字节是1,说明数字1的低位字节(就是1本身)存放在了低地址,那就是小端
  5. 如果第一个字节是0,说明数字1的高位字节(一堆0)存放在了低地址,那就是大端

是不是很巧妙?这个小练习完美体现了联合体“共享内存”的特性。

2. 枚举 (Enum) —— “让代码会说话”的命名大师

讲完了“内存共享大师”联合体,我们再来认识一下“代码可读性大师”——枚举(Enumeration)

2.1 什么是枚举?—— 给数字穿上“有意义的外衣”

枚举,顾名思义,就是一一列举

在编程中,我们经常会遇到一些变量,它的取值是有限的、固定的几个选项

比如:

  • 一周有7天:星期一、星期二...星期日。
  • 性别:男、女、保密。
  • 交通灯:红、黄、绿。
  • 游戏角色状态:站立、行走、奔跑、跳跃、攻击。

在没有枚举之前,我们可能会用#define宏或者直接用数字来表示:

#define MONDAY 0
#define TUESDAY 1
#define WEDNESDAY 2
// ... 以此类推int today = MONDAY;
if (today == MONDAY) {printf("又是周一,不想上班!\n");
}

或者更偷懒的:

int today = 0; // 0代表周一
if (today == 0) {printf("又是周一,不想上班!\n");
}

这样写有什么问题?

  1. 可读性差:看到if (today == 0),你得去翻半天注释或者宏定义才知道0代表周一。时间久了,你自己都可能忘记。
  2. 容易出错:万一手滑写成if (today == 7),而7并没有定义,程序可能出错或者产生不符合预期的结果。
  3. 维护困难:如果你想在星期一和星期二之间加个“星期一点五”,你需要手动调整后面所有宏定义的值,非常麻烦。

枚举就是为了解决这些问题而生的!

// 声明一个枚举类型,叫 Day (星期)
enum Day {Mon,  // 星期一Tues, // 星期二Wed,  // 星期三Thur, // 星期四Fri,  // 星期五Sat,  // 星期六Sun   // 星期日
};// 声明一个枚举变量
enum Day today = Mon;if (today == Mon) {printf("又是周一,不想上班!\n");
}

发生了什么变化?

  • 我们用enum Day定义了一种新的“数据类型”,它专门用来表示星期。
  • {}里面列举了这种类型所有可能的取值:Mon, Tues, Wed... 这些叫做枚举常量
  • 默认情况下,编译器会给这些常量自动分配整数值,从0开始递增。所以Mon=0, Tues=1, ..., Sun=6
  • 在代码中,我们可以直接用MonTues这些有意义的名字,而不是冷冰冰的数字0、1。

效果: 代码瞬间变得清晰易懂!if (today == Mon),一看就知道是在判断是不是星期一,根本不需要注释!

2.2 枚举常量的值可以自定义

虽然默认从0开始,但我们可以手动指定。

enum Color {RED = 2,GREEN = 4,BLUE = 8
};

这样,RED的值就是2,GREEN是4,BLUE是8。为什么要这么设计?有时候是为了和硬件寄存器、网络协议等已有的数值规范对齐。

你也可以只给部分赋值,未赋值的会从上一个的值+1开始:

enum Status {OFF = 0,ON,      // 未赋值,默认是 0+1 = 1STANDBY, // 未赋值,默认是 1+1 = 2ERROR = 100,FATAL    // 未赋值,默认是 100+1 = 101
};

2.3 为什么用枚举?—— 它的五大优点

  1. 增加代码的可读性和可维护性 (最重要!) 这是枚举最大的价值。if (status == ERROR)if (status == 3) 好懂得多。半年后你回来看代码,或者你的同事接手你的项目,都能快速理解。维护起来也方便,想改某个状态的值,在枚举定义里改一下就行,不用全局搜索替换数字。

  2. 有类型检查,更严谨 #define定义的宏,在编译预处理阶段就被替换成数字了,编译器不知道它原来代表什么含义。 枚举则不同,enum Day today; 明确告诉编译器,today是一个“星期”类型的变量。如果你不小心写 today = 100; (100不是一个合法的星期),一些严格的编译器(或在C++中)会发出警告。这能帮你提前发现潜在的逻辑错误。

  3. 便于调试 当你用调试器(Debugger)查看变量时,如果变量是枚举类型,调试器通常会显示MonTues这样的名字,而不是0、1。这能让你更快地定位问题。

  4. 使用方便,一次定义多个常量#define,你得写7行来定义一周的7天。用枚举,一个{}就搞定了,整洁又高效。

  5. 遵循作用域规则 和变量一样,枚举类型可以定义在函数内部、全局等不同作用域,管理起来更灵活。

2.4 枚举怎么用?—— 实战演练

基本用法:定义、声明、赋值
#include <stdio.h>// 1. 声明枚举类型
enum TrafficLight {RED,YELLOW,GREEN
};int main() {// 2. 声明枚举变量enum TrafficLight current_light;// 3. 给枚举变量赋值current_light = RED;// 4. 使用枚举常量进行判断if (current_light == RED) {printf("红灯停!\n");} else if (current_light == YELLOW) {printf("黄灯等一等!\n");} else if (current_light == GREEN) {printf("绿灯行!\n");}return 0;
}
一个更复杂的例子:游戏角色状态
#include <stdio.h>enum PlayerState {IDLE,    // 空闲/站立WALKING, // 行走RUNNING, // 奔跑JUMPING, // 跳跃ATTACKING // 攻击
};void update_player_animation(enum PlayerState state) {switch (state) {case IDLE:printf("播放站立动画\n");break;case WALKING:printf("播放行走动画\n");break;case RUNNING:printf("播放奔跑动画\n");break;case JUMPING:printf("播放跳跃动画\n");break;case ATTACKING:printf("播放攻击动画\n");break;default:printf("未知状态,播放默认动画\n");}
}int main() {enum PlayerState player = IDLE;update_player_animation(player); // 输出: 播放站立动画player = RUNNING;update_player_animation(player); // 输出: 播放奔跑动画return 0;
}

在这个例子中,update_player_animation函数的参数明确要求是enum PlayerState类型,这使得函数的意图非常清晰。调用者也只能传入IDLEWALKING等预定义的状态,减少了传入非法值的可能性。

关于“整数赋值”的小插曲

讲义最后提到一个细节:

那是否可以拿整数给枚举变量赋值呢?在C语言中是可以的,但是在C++是不行的,C++的类型检查比较严格。

是的,在C语言中,下面的代码是合法的:

enum Color clr;
clr = 2; // C语言允许,但强烈不建议!

虽然合法,但这完全违背了我们使用枚举的初衷!你又把清晰的代码变回了模糊的数字。所以,作为一个有追求的程序员,请永远使用枚举常量(如RED, GREEN)来给枚举变量赋值,不要用数字!

在C++中,编译器会阻止你这样做,强制你写出更安全、更清晰的代码。

总结:

我们花了这么长的篇幅,终于把联合体和枚举这两个“自定义类型”讲完了。让我们最后再总结一下它们的精髓:

联合体 (Union):内存的“共享公寓”

  • 核心思想:所有成员共用同一块内存
  • 目的节省内存空间。特别适用于那些成员不会同时被使用的情况(如礼品系统的不同商品属性)。
  • 特点:改一个,动全身。要时刻记住,你操作的是同一块内存的不同“视角”。
  • 大小计算:至少是最大成员的大小,并且要考虑内存对齐。
  • 使用场景:嵌入式开发、网络协议解析、需要极致优化内存的场合。

枚举 (Enum):代码的“翻译官”

  • 核心思想:为一组相关的整型常量提供有意义的名字
  • 目的大幅提升代码的可读性、可维护性和安全性
  • 特点:默认从0开始递增,值可自定义。有类型检查(比#define强)。
  • 使用场景:任何有固定、有限选项的地方!星期、月份、状态、颜色、方向... 无处不在!

当然,这是C语言入门指南的最后一篇啦,完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

后续会更新数据结构及C++的内容

敬请期待哦

加油!期待看到你写出更棒的代码!

http://www.dtcms.com/a/393159.html

相关文章:

  • JS逆向 -去哪儿滑块
  • C++包装器(Wrapper)概述
  • java后端工程师进修ing(研一版‖day47)
  • 小谈:物联网(IoT)与工业传感器技术
  • python标准库
  • 01 Tasking软件安装及新建工程
  • ​​[硬件电路-288]: 单路双输入异或门(门控开关:两个同时为1,输出为1)NC7SZ86L6X 功能概述与管脚定义
  • 声明式事务4
  • 速通ACM省铜第十天 赋源码(A Good Problem和Make It Beautiful和Gellyfish and Baby‘s Breath)
  • 寰宇光锥舟
  • 如何安全的计算softmax?
  • 第一部分:基础架构与入门
  • [Windows] Cencrack在线工具包6.52
  • 算法基础篇(2)模拟
  • Go基础:Go语言结构体(Struct)和接口(Interface)详解
  • 【计算机毕业设计】基于生成对抗网络的动作与表情一致性动漫角色生成算法系统​
  • html5 做个人网页识芯平夹回拼翘
  • 开收价均值策略
  • 【大模型部署】Ollama部署gguf模型
  • Coze源码分析-资源库-删除工作流-前端源码-核心组件
  • 机器学习实战第八章 降维
  • 2025年csp-j真题和解析
  • C++ STL map 深度解析:从原理到实战的全方位指南
  • 【EKF组合导航例程】MATLAB代码,15维状态量、3维观测量的组合导航,滤波使用EKF(扩展卡尔曼滤波)。附下载链接
  • word文档怎么根据大纲拆分章节
  • 【Modbus】Modbus协议基础知识详解
  • Springboot使用Integration实现MQTT发送和接收消息
  • 中国传统文化上衣下裳
  • zk管理kafkakafka-broker通信
  • 前端开发技术趋势Web Components