大家好,我是网域小星球。
前面我们学习了结构体 ------ 一种能打包多种不同类型数据的自定义类型,解决了复杂对象的描述问题。而在实际编程中,我们还会遇到两种特殊场景:
- 一块内存空间,同时只能存储一种数据(节省内存);
- 变量的取值,只能是固定的几个常量(规范取值,避免出错)。
C 语言提供的「共用体」和「枚举」,就专门解决这两个问题。它们和结构体同属自定义类型,用法简单、场景明确,是进阶编程和笔试面试的基础知识点。本篇全程 VS2022 可直接运行,从概念到实战,一步步吃透。
目录
[2.1 什么是共用体?](#2.1 什么是共用体?)
[2.2 共用体的定义语法](#2.2 共用体的定义语法)
[2.3 共用体的初始化与使用](#2.3 共用体的初始化与使用)
[2.4 共用体的核心特性与适用场景](#2.4 共用体的核心特性与适用场景)
[2.5 共用体与结构体的区别(重点)](#2.5 共用体与结构体的区别(重点))
[3.1 什么是枚举?](#3.1 什么是枚举?)
[3.2 枚举的定义语法](#3.2 枚举的定义语法)
[3.3 枚举变量的定义与使用](#3.3 枚举变量的定义与使用)
[3.4 枚举的核心特性与适用场景](#3.4 枚举的核心特性与适用场景)
[四、综合实战案例(VS2022 可直接运行)](#四、综合实战案例(VS2022 可直接运行))
一、本章学习目标
学完本篇你将彻底掌握:
- 共用体(Union)的定义、初始化与使用,理解 "共享内存" 的核心特性;
- 共用体与结构体的区别,明确共用体的适用场景;
- 枚举(Enum)的定义、初始化与使用,掌握固定常量的定义方式;
- 枚举的实际应用,避免 "魔法数字",让代码更规范、易读;
- 共用体与枚举的实战案例,学会在项目中灵活运用。
二、共用体(Union):共享内存的自定义类型
2.1 什么是共用体?
共用体,也叫联合体,核心特点是:所有成员共享同一块内存空间,同一时间只能存储一个成员的值。
- 结构体:所有成员 "各自拥有独立内存",总大小是所有成员大小之和;
- 共用体:所有成员 "共享一块内存",总大小是最大成员的大小(保证能容纳任何一个成员)。
可以形象理解:
- 结构体 → 多个独立的房间(每个成员一间房);
- 共用体 → 一个房间,轮流住不同的人(每个成员轮流使用这块内存)。
2.2 共用体的定义语法
和结构体语法类似,只需把struct换成union:
cpp
// 定义共用体类型
union 共用体名
{
数据类型 成员1;
数据类型 成员2;
...
};
示例:定义一个能存储 int、char、float 的共用体
cpp
// 共用体:同一时间只能存储一个类型的值
union Data
{
int a; // 4字节
char b; // 1字节
float c; // 4字节
};
这个共用体的大小是 4 字节(最大成员 int 和 float 都是 4 字节),而非 4+1+4=9 字节。
2.3 共用体的初始化与使用
(1)初始化
共用体只能初始化一个成员(因为同一时间只能存储一个值),语法和结构体类似:
cpp
// 初始化共用体,只能给一个成员赋值
union Data d1 = {10}; // 初始化成员a(int类型)
union Data d2 = {.b = 'A'}; // 指定初始化成员b(char类型)
union Data d3 = {.c = 3.14f}; // 指定初始化成员c(float类型)
(2)成员访问
和结构体一样,用.访问成员;如果是共用体指针,用->访问:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
union Data
{
int a;
char b;
float c;
};
int main()
{
union Data d;
// 给成员a赋值,此时共用体存储的是int类型
d.a = 0x12345678;
printf("d.a = %d\n", d.a); // 输出:305419896(0x12345678对应的十进制)
// 访问成员b(共享同一块内存,值会受a的影响)
printf("d.b = %c\n", d.b); // 输出:x(0x78对应的ASCII值)
// 给成员c赋值,会覆盖a的值(共用体只能存一个值)
d.c = 3.14f;
printf("d.c = %.2f\n", d.c); // 输出:3.14
printf("d.a = %d\n", d.a); // 输出:随机值(被c覆盖)
return 0;
}
2.4 共用体的核心特性与适用场景
核心特性
- 共享内存:所有成员占用同一块内存,修改一个成员,会覆盖其他成员的值;
- 大小 = 最大成员的大小:节省内存空间;
- 同一时间只能使用一个成员:不能同时给多个成员赋值并使用。
适用场景
- 节省内存:当数据之间 "互斥"(同一时间只需要存储一种)时,比如:
- 存储学生的 "成绩类型":要么是 int(百分制),要么是 char(等级制 A/B/C);
- 存储设备数据:要么是 int(整数指令),要么是 float(浮点数据)。
- 类型转换:利用共享内存的特性,实现不同类型数据的强制转换(进阶用法)。
2.5 共用体与结构体的区别(重点)
| 特性 | 结构体(struct) | 共用体(union) |
|---|---|---|
| 内存分配 | 所有成员独立分配内存 | 所有成员共享一块内存 |
| 总大小 | 所有成员大小之和(含内存对齐) | 最大成员的大小 |
| 成员使用 | 可同时使用所有成员 | 同一时间只能使用一个成员 |
| 核心用途 | 描述复杂对象(多种数据共存) | 节省内存(互斥数据存储) |
三、枚举(Enum):固定常量的自定义类型
3.1 什么是枚举?
枚举,就是 "列举",核心作用是:定义一组固定的、有名字的常量,让代码更规范、易读,避免直接使用 "魔法数字"(无意义的数字常量)。
比如:
- 一周的天数:周一~周日(固定 7 个值);
- 性别:男、女、未知(固定 3 个值);
- 状态:成功、失败、等待(固定 3 个值)。
这些场景用枚举定义,比直接写 1、2、3 更易理解,也能避免写错值。
3.2 枚举的定义语法
cpp
// 定义枚举类型
enum 枚举名
{
常量1,
常量2,
...
常量n
};
示例 1:定义 "星期" 枚举
cpp
// 枚举:一周的七天,固定7个常量
enum Week
{
Monday, // 默认值0
Tuesday, // 1(自动递增1)
Wednesday, // 2
Thursday, // 3
Friday, // 4
Saturday, // 5
Sunday // 6
};
示例 2:自定义枚举值
cpp
// 自定义枚举值,未指定的自动递增
enum Gender
{
Male = 1, // 自定义值1
Female = 2, // 自定义值2
Unknown // 自动为3
};
3.3 枚举变量的定义与使用
(1)定义枚举变量
有 3 种常用方式,推荐前两种(更规范):
cpp
// 方式1:先定义枚举类型,再定义变量
enum Week w1;
// 方式2:定义枚举类型的同时,定义变量
enum Week w2, w3;
// 方式3:省略枚举名(不推荐,无法重复使用该枚举类型)
enum
{
Success,
Failed,
Waiting
} status;
(2)使用枚举变量
枚举变量的取值,只能是枚举中定义的常量,不能赋值其他值(编译器会警告):
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
// 定义枚举:状态
enum Status
{
Success = 0, // 成功
Failed = 1, // 失败
Waiting = 2 // 等待
};
int main()
{
enum Status s = Success;
// 枚举变量赋值(只能用枚举常量)
s = Waiting;
// 用switch判断枚举值(最常用场景)
switch (s)
{
case Success:
printf("操作成功!\n");
break;
case Failed:
printf("操作失败!\n");
break;
case Waiting:
printf("操作等待中...\n"); // 输出此句
break;
default:
printf("无效状态!\n");
}
return 0;
}
3.4 枚举的核心特性与适用场景
核心特性
- 枚举常量是常量 ,不能修改(比如不能写
Monday = 10); - 枚举变量的取值,只能是枚举中定义的常量,超出范围会有警告;
- 枚举常量默认从 0 开始,依次递增 1;也可以自定义每个常量的值;
- 枚举本质是 int 类型,枚举常量的值可以参与整数运算。
适用场景
- 固定取值的场景:星期、性别、状态、颜色等;
- 替代 "魔法数字":比如用
Success替代 0,Failed替代 1,让代码更易读、易维护; - 函数参数限制:让函数参数只能取固定值,避免传入无效参数(进阶用法)。
四、综合实战案例(VS2022 可直接运行)
结合共用体和枚举,实现一个 "学生成绩录入" 程序,支持两种成绩类型(百分制 / 等级制),用枚举限制成绩类型,用共用体存储成绩:
cpp
#define _CRT_SECURE_NO_WARNINGS 1<stdio.h>
// 枚举:成绩类型(百分制/等级制)
enum ScoreType
{
Percent = 1, // 百分制(int)
Grade // 等级制(char),自动为2
};
// 共用体:存储成绩(同一时间只能存一种类型)
union Score
{
int percent; // 百分制成绩(0-100)
char grade; // 等级制成绩(A/B/C/D/E)
};
// 结构体:学生信息(包含成绩类型和成绩)
struct Student
{
int id;
char name[20];
enum ScoreType type; // 成绩类型
union Score score; // 成绩(共用体)
};
int main()
{
struct Student stu;
printf("请输入学生信息:\n");
printf("学号:");
scanf("%d", &stu.id);
printf("姓名:");
scanf("%s", stu.name);
printf("成绩类型(1=百分制,2=等级制):");
scanf("%d", &stu.type);
// 根据成绩类型,录入对应成绩
if (stu.type == Percent)
{
printf("请输入百分制成绩(0-100):");
scanf("%d", &stu.score.percent);
}
else if (stu.type == Grade)
{
printf("请输入等级制成绩(A-E):");
scanf(" %c", &stu.score.grade); // 空格避免读取换行符
}
else
{
printf("无效的成绩类型!\n");
return 0;
}
// 输出学生信息
printf("\n学生信息如下:\n");
printf("学号:%d\n", stu.id);
printf("姓名:%s\n", stu.name);
printf("成绩:");
if (stu.type == Percent)
{
printf("%d分(百分制)\n", stu.score.percent);
}
else
{
printf("%c级(等级制)\n", stu.score.grade);
}
return 0;
}
五、高频易错点
- 共用体使用误区:误以为能同时存储多个成员的值,修改一个成员后,其他成员的值会被覆盖;
- 共用体初始化错误:同时给多个成员赋值,编译器会报错或警告;
- 枚举使用误区:给枚举变量赋值枚举外的常量,虽然可能运行,但不符合规范,易出错;
- 混淆枚举与结构体:误用共用体存储共存的数据(比如同时存储学生的年龄和成绩),导致数据错乱;
- 枚举常量修改:试图修改枚举常量的值(比如
Sunday = 10),编译器会报错。
六、本章核心总结
- 共用体(Union):所有成员共享一块内存,同一时间只能存储一个成员,核心作用是节省内存;
- 共用体大小 = 最大成员的大小,与结构体的 "成员内存独立" 有本质区别;
- 枚举(Enum):定义一组固定常量,核心作用是规范取值、避免魔法数字,让代码更易读;
- 枚举常量默认从 0 递增,可自定义值,枚举变量只能取枚举中定义的常量;
- 共用体 + 枚举 + 结构体,可实现更灵活、规范的自定义数据管理,是进阶编程的基础。
下期预告
下一篇我们进入指针进阶内容,拆解易混的三大指针:指针数组、数组指针、函数指针,掌握它们的定义、区别与实战用法,攻克 C 语言指针的核心难点。