目录
[(三) 联合体大小的计算](#(三) 联合体大小的计算)
[1、场景 1:礼品兑换单设计(节省内存)](#1、场景 1:礼品兑换单设计(节省内存))
[2、场景 2:判断机器大小端存储](#2、场景 2:判断机器大小端存储)
[1、场景 1:switch-case 分支(计算器菜单优化)](#1、场景 1:switch-case 分支(计算器菜单优化))
[2、场景 2:函数参数限制(限定输入范围)](#2、场景 2:函数参数限制(限定输入范围))
[(四)枚举与 #define 的对比(优点)](#define 的对比(优点))
一、联合体
联合体(union,又称 "共用体")是一种特殊的自定义类型,所有成员共享同一块内存空间,核心价值是在特定场景下节省内存(避免存储互斥的冗余数据)。
像结构体⼀样,联合体也是由一个或者多个成员构成,这些成员可以不同的类型。
(一)联合体类型的定义
1、语法格式
cpp
// 定义方式
union 联合体类型名 {
成员类型1 成员名1;
成员类型2 成员名2;
...
成员类型n 成员名n;
};
// 声明方式
union 联合体类型名; // 仅声明联合体的存在,不定义其成员(称为"不完全类型")
2、关键字与内存分配原则
**(1)关键字:**union(对比结构体 struct、枚举 enum)。
(2)内存分配原则: 编译器仅为最大成员分配足够的内存空间(所有成员共享此空间)。
3、示例:基础联合体定义与内存验证
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 声明联合体:包含 char(1字节)和 int(4字节)
union Un {
char c; // 成员1:1字节
int i; // 成员2:4字节
};
int main() {
union Un un;
// 1. 计算联合体大小:最大成员(int)的大小,结果为 4
printf("联合体大小:%zd\n", sizeof(un));
// 2. 验证成员共享内存:所有成员的起始地址相同
printf("&un = %p\n", &un); // 联合体变量地址
printf("&un.c = %p\n", &un.c); // 成员c的地址
printf("&un.i = %p\n", &un.i); // 成员i的地址
return 0;
}

输出结果:三个地址完全相同,证明成员共享内存。
(二)联合体的核心特点
1、内存共享
所有成员共用同一块空间,修改一个成员会覆盖其他成员的值。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
union Un {
char c;
int i;
};
int main() {
union Un un;
un.i = 0x11223344; // 给int成员赋值(4字节)
printf("修改前 un.c = 0x%x\n", un.c); // 输出 0x44(小端存储下的首字节)
un.c = 0x55; // 修改char成员(覆盖首字节)
printf("修改后 un.i = 0x%x\n", un.i); // 输出 0x11223355(首字节被改为55)
return 0;
}


2、瞬时独占性
同一时间只能有效使用一个成员(因为修改一个会影响其他),需自行维护 "当前有效成员" 的逻辑。
3、与结构体的本质区别
| 对比维度 | 结构体(struct) | 联合体(union) |
|---|---|---|
| 内存分配方式 | 为每个成员单独分配独立的内存空间 | 所有成员共享同一块内存空间 |
| 成员独立性 | 成员相互独立,修改一个成员不会影响其他成员 | 成员互斥,修改一个成员会覆盖其他成员的值 |
| 内存占用大小 | 所有成员内存大小之和 (需考虑内存对齐) | 等于联合体中最大成员的内存大小(需考虑内存对齐) |
| 适用场景 | 存储关联且需同时存在、同时访问的数据(例如:学生信息包含的姓名、年龄、学号、成绩) | 存储互斥且不同时使用的数据 (例如:商品属性中,"重量" 与 "体积" 仅需保留其一,或 "整数价格" 与 "小数价格" 二选一) |

(三) 联合体大小的计算
1、两个规则
联合体大小需遵循两个规则,本质是平衡内存利用率与 CPU 访问效率(内存对齐):
(1)基础规则:联合体大小至少是最大成员的大小。(需容纳最大成员)
(2)对齐规则:若最大成员的大小 不是最大对齐数的整数倍 ,需填充内存至最大对齐数的整数倍。(为了让所有成员都能满足自身对齐要求)
(3)关键概念:对齐数
① 每个成员的对齐数 = 编译器默认对齐数(通常为 4 或 8)与成员自身大小的较小值。
② 联合体的最大对齐数 = 所有成员对齐数中的最大值。
2、具体示例
(1)示例 1:含数组与 int 的联合体
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 联合体:char arr[5](5字节,对齐数1)、int i(4字节,对齐数4)
union Un1 {
char arr[5]; // 成员1:5字节,对齐数1
int i; // 成员2:4字节,对齐数4
};
int main() {
// 计算大小:最大成员大小5,最大对齐数4;5不是4的倍数,填充至8(4×2)
printf("union Un1 大小:%zd\n", sizeof(union Un1)); // 输出 8
return 0;
}
(2)示例 2:含短整型数组与 int 的联合体
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 联合体:short arr[7](14字节,对齐数2)、int i(4字节,对齐数4)
union Un2 {
short arr[7]; // 成员1:7×2=14字节,对齐数2
int i; // 成员2:4字节,对齐数4
};
int main() {
// 计算大小:最大成员大小14,最大对齐数4;14不是4的倍数,填充至16(4×4)
printf("union Un2 大小:%zd\n", sizeof(union Un2)); // 输出 16
return 0;
}
(四)联合体的典型应用场景
1、场景 1:礼品兑换单设计(节省内存)
(1)问题
礼品兑换单需描述三种商品(图书、杯子、衬衫),它们有公共属性(库存量、价格、类型),但特殊属性互斥(图书需书名 / 作者,杯子需设计,衬衫需设计 / 颜色 / 尺寸)。若用结构体存储所有属性,会浪费大量内存(如描述图书时,杯子 / 衬衫的特殊属性空间闲置)。
(2)解决方案:
用联合体存储特殊属性(互斥数据共享空间),结构体存储公共属性。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 礼品兑换单结构体:使用 "公共属性+联合体存储特殊属性" 的设计
struct GiftList {
int stock_number; // 公共属性:库存量
float price; // 公共属性:价格
char type; // 公共属性:商品类型('B'=图书,'M'=杯子,'S'=衬衫)
// 联合体:存储互斥的特殊属性,成员共享内存
union {
struct {
char title[50]; // 特殊属性:书名
char author[30]; // 特殊属性:作者
int pages; // 特殊属性:页数
} book;
struct {
char design[30]; // 特殊属性:设计图案
} mug;
struct {
char design[30]; // 特殊属性:设计图案
char color[20]; // 特殊属性:可选颜色
char size[10]; // 特殊属性:可选尺寸
} shirt;
} info;
};
int main() {
// 1. 初始化图书数据
struct GiftList book_gift = {
.stock_number = 100, // 图书库存
.price = 59.9f, // 图书价格(补充打印)
.type = 'B', // 商品类型(图书)
.info.book = { // 初始化图书特殊属性,符合文件联合体初始化规则
"C语言程序设计", // 书名
"张三", // 作者
300 // 页数(补充打印)
}
};
// 完整打印图书所有属性:公共属性(库存、价格、类型)+ 特殊属性(书名、作者、页数)
printf("==================== 图书信息 ====================\n");
printf("商品类型:%c('B'=图书)\n", book_gift.type);
printf("书名:《%s》\n", book_gift.info.book.title);
printf("作者:%s\n", book_gift.info.book.author);
printf("页数:%d 页\n", book_gift.info.book.pages);
printf("库存量:%d 本\n", book_gift.stock_number);
printf("单价:%.1f 元\n", book_gift.price); // 浮点型打印保留1位小数,更直观
printf("==================================================\n");
printf("\n");
// 2. 初始化衬衫数据
struct GiftList shirt_gift = {
.stock_number = 50, // 衬衫库存
.price = 99.0f, // 衬衫价格(补充打印)
.type = 'S', // 商品类型(衬衫)
.info.shirt = { // 初始化衬衫特殊属性,符合文件联合体初始化规则
"条纹", // 设计图案
"白色/蓝色", // 可选颜色
"M/L/XL" // 可选尺寸(补充打印)
}
};
// 完整打印衬衫所有属性:公共属性(库存、价格、类型)+ 特殊属性(设计、颜色、尺寸)
printf("==================== 衬衫信息 ====================\n");
printf("商品类型:%c('S'=衬衫)\n", shirt_gift.type);
printf("设计图案:%s\n", shirt_gift.info.shirt.design);
printf("可选颜色:%s\n", shirt_gift.info.shirt.color);
printf("可选尺寸:%s\n", shirt_gift.info.shirt.size); // 补充尺寸打印
printf("库存量:%d 件\n", shirt_gift.stock_number);
printf("单价:%.1f 元\n", shirt_gift.price); // 浮点型打印保留1位小数
printf("==================================================\n");
return 0;
}
2、场景 2:判断机器大小端存储
(1)大小端定义
**① 大端存储:**数据的高位字节存放在低地址(如 0x11223344 存储为 11 22 33 44)。
**② 小端存储:**数据的低位字节存放在低地址(如 0x11223344 存储为 44 33 22 11)。
(2)核心思路
利用联合体成员共享内存的特性,给 int 成员赋值 1(二进制 0x00000001),通过 char 成员读取首字节值:
若首字节为 1(低地址存低位字节),则为小端。
若首字节为 0(低地址存高位字节),则为大端。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 函数:判断大小端,返回1=小端,0=大端
int check_endian() {
// 定义匿名联合体(仅用一次,无需命名)
union {
int i; // 4字节成员
char c; // 1字节成员(共享首字节)
} un;
un.i = 1; // 给int赋值1,二进制 0x00000001
return un.c; // 读取首字节值:1=小端,0=大端
}
int main() {
if (check_endian() == 1) {
printf("当前机器为小端存储\n");
} else {
printf("当前机器为大端存储\n");
}
return 0;
}
优势 :代码简洁 (无需指针强制类型转换),逻辑直观(利用联合体内存共享特性)。
二、枚举
枚举类型(enum)是 C 语言中一种自定义类型,通过为整型常量赋予语义化名称,解决 "魔法数字" 导致的代码可读性差、维护成本高的问题,核心价值在于提升代码的直观性与可维护性。
(一)枚举的定义与语法
enum 枚举类型名 { 枚举常量1, 枚举常量2, ..., 枚举常量n };
1、枚举常量与枚举变量
(1)枚举常量
① 默认从 0 开始自动递增;
② 也可显式指定值,支持跳跃赋值,后续常量会基于前一个值递增。
cpp
// 计算器功能选项(显式赋值,与菜单数字对应)
enum Option {
EXIT = 0, // 退出(对应菜单0)
ADD = 1, // 加法(对应菜单1)
SUB = 2, // 减法(对应菜单2)
MUL = 3, // 乘法(对应菜单3)
DIV = 4 // 除法(对应菜单4)
};
// 状态机(默认从0递增)
enum State {
RUNNING, // 0:运行中
PAUSED, // 1:暂停
STOPPED // 2:停止
};
关键特性:
枚举常量本质是整型,但通过命名体现语义(如 ADD 比 1 更直观)。C 语言中枚举常量可隐式转换为 int,但 C++ 中类型检查严格,需显式转换,如 (int)ADD 。
(2)枚举变量
通过枚举类型创建变量,如enum Week today = MONDAY;,变量仅能赋值为枚举常量。下面通过一个完整的例子来理解枚举变量的赋值规则:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 定义枚举类型enum Week,包含7个枚举常量
enum Week {
ZERO,
MONDAY, // 枚举常量,默认值0
TUESDAY, // 枚举常量,默认值1
WEDNESDAY, // 枚举常量,默认值2
THURSDAY, // 枚举常量,默认值3
FRIDAY, // 枚举常量,默认值4
SATURDAY, // 枚举常量,默认值5
SUNDAY // 枚举常量,默认值6
};
int main() {
// 创建枚举变量today,类型是enum Week
enum Week today;
// 正确赋值:使用enum Week中定义的枚举常量
today = MONDAY; // 合法,将"周一"赋值给today
printf("今天是周几:%d\n", today); // 输出:今天是周几:0
today = FRIDAY; // 合法,将"周五"赋值给today
printf("今天是周几:%d\n", today); // 输出:今天是周几:4
// 错误赋值:使用非枚举常量(C语言可能警告,C++直接报错)
today = 3; // 不推荐:虽然3对应THURSDAY,但失去了枚举的可读性
today = 10; // 错误:10不是enum Week中定义的常量(超出范围)
today = "周一"; // 错误:类型不匹配(字符串不能赋值给枚举变量)
return 0;
}
(二)枚举常量的自定义赋值
可显式指定枚举常量的值,未指定的常量会在前一个常量的基础上递增 1;
注意这不是在改动它,而是给它增加一个初始值,枚举里面的值是不能被修改的。
cpp
enum Week {
MON = 1, // 1
TUE, // 2(1+1)
WED = 5, // 5
THU // 6(5+1)
};
(三)枚举的应用场景
1、场景 1:switch-case 分支(计算器菜单优化)
**(1)问题:**原始代码用数字 1/2/3/4 对应加减乘除,需记忆数字与功能的映射,可读性差。
(2)优化方案:用枚举常量替代数字,逻辑直观且无需记忆映射关系。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 定义枚举类型
enum Option {
EXIT = 0,
ADD = 1,
SUB = 2,
MUL = 3,
DIV = 4
};
int main() {
int input = 0;
do {
// 打印菜单
printf("1. 加法\n2. 减法\n3. 乘法\n4. 除法\n0. 退出\n请选择:");
scanf("%d", &input);
// 用枚举常量判断分支
switch (input) {
case ADD: // 直观:直接对应"加法"
printf("执行加法\n");
break;
case SUB: // 直观:直接对应"减法"
printf("执行减法\n");
break;
case MUL:
printf("执行乘法\n");
break;
case DIV:
printf("执行除法\n");
break;
case EXIT:
printf("退出程序\n");
break;
default:
printf("选择错误,请重新输入\n");
}
} while (input != EXIT);
return 0;
}

2、场景 2:函数参数限制(限定输入范围)
通过枚举类型作为函数参数,强制调用者只能传入预定义的合法值,减少非法输入。在C语言中,为枚举类型的函数参数赋值,最好使用枚举常量;虽然在C语言中可以传入非枚举常量,但是在C++中是不允许的。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> // 包含printf函数所需头文件
// 1. 定义枚举类型Color,显式指定常量值(参考文件枚举声明语法)
enum Color {
RED = 1, // 红色,值为1
GREEN = 2, // 绿色,值为2
BLUE = 4 // 蓝色,值为4
};
// 2. 定义设置文本颜色的函数,参数为枚举类型Color(参考文件枚举函数参数用法)
void setColor(enum Color clr) {
// 通过switch-case匹配枚举常量,实现语义化分支(参考文件计算器案例逻辑)
switch (clr) {
case RED:
printf("设置为红色\n");
break; // 跳出分支,避免穿透
case GREEN:
printf("设置为绿色\n");
break;
case BLUE:
printf("设置为蓝色\n");
break;
default: // 处理非法枚举值(C中可能传入非枚举常量,增加健壮性)
printf("无效的颜色选项,请传入RED、GREEN或BLUE\n");
}
}
// 3. 主函数:调用setColor函数,验证枚举类型使用
int main() {
printf("=== 测试枚举类型设置文本颜色 ===\n");
// 正确调用:传入枚举常量(参考文件枚举变量赋值规则)
setColor(RED); // 输出"设置为红色"
setColor(GREEN); // 输出"设置为绿色"
setColor(BLUE); // 输出"设置为蓝色"
// 错误示例(C中允许,C++中报错):传入非枚举常量(参考文件枚举类型安全说明)
// setColor(5); // 编译运行后输出"无效的颜色选项,请传入RED、GREEN或BLUE"
return 0;
}
(四)枚举与 #define 的对比(优点)
1、增加代码的可读性和可维护性。
2、和 #define 定义的标识符比较枚举有类型检查,更加严谨。
3、便于调试,预处理阶段会删除 #define 定义的符号。
4、使用方便,一次可以定义多个常量。
5、枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使使用。
| 对比维度 | 枚举 | #define |
|---|---|---|
| 类型检查 | 有类型检查(C++ 中严格禁止整型与枚举类型隐式转换) | 无类型检查,仅文本替换 |
| 调试支持 | 保留符号信息,调试时可看到常量名(如 MALE) | 预处理阶段删除符号,调试时仅显示数值(如 0) |
| 批量定义 | 单次声明可定义多个关联常量,语法简洁 | 需逐一定义,繁琐且易出错 |
| 作用域 | 遵循块作用域(函数内声明的枚举仅限函数内使用) | 全局作用域,可能引发命名冲突 |
枚举的优势总结:
**① 消除魔法数字:**用 ADD 替代 1,无需记忆数值与功能的映射。
**② 增强可维护性:**若菜单功能调整(如新增 "取余"),仅需修改枚举定义,无需全局替换数字。
**③ 提升可读性:**代码逻辑直观,新手可快速理解 case ADD 对应 "加法" 操作。
三、联合体与枚举总结
| 类型 | 核心特性 | 关键优势 | 典型应用场景 |
|---|---|---|---|
| 联合 | 所有成员共享内存空间 | 节省内存(存储互斥数据) | 协议解析、变体记录、大小端判断 |
| 枚举 | 为整型常量赋予语义化名称 | 提升可读性、降低维护成本 | 菜单选项、状态机、配置参数 |
枚举与联合体均为 "轻量级" 自定义类型,合理使用可显著提升代码质量。
枚举 解决 "语义模糊" 问题,联合体****解决 "内存浪费" 问题,二者常结合结构体在实际项目中(如嵌入式协议、数据解析、资源管理)发挥重要作用。
以上即为 自定义类型:联合体与枚举 的全部内容,麻烦三连支持一下呗~
