第十三章 共用体与枚举
本章要点
第五章学习的结构体能将多个不同类型的数据打包,每个成员拥有独立的内存空间。本章讨论另外两种构造类型:共用体和枚举。
- 共用体(union):多个成员共享同一块内存,同一时刻只能存一个成员的值
- 枚举(enum):给整型常量起有意义的名字,替换“魔法数字“
- 两者组合成**“带标签的联合体”**——用枚举标记共用体当前存的是哪种类型
一、共用体
1. 共用体(union)
1.1 从一种“多功能“储物格说起
结构体可以把不同类型的数据同时打包在一起,每个成员都有自己独立的空间。但有时我们需要的是一个“多合一“的存储单元:同一块空间,在不同的时候可以存放不同类型的数据,但一次只能容纳一种。
设想这样一个场景:一个变量在程序运行期间,有时需要存储整数,有时需要存储浮点数,有时需要存储字符——但这三种需求绝不会同时出现。如果用结构体来实现,三个成员各自占用各自的空间,会造成不必要的浪费。
共用体正是为这种场景设计的:所有成员共享同一块内存,你往其中写入一个新成员的值,就会覆盖掉旧成员的内容。
1.2 语法基本规则
共用体的定义语法和结构体非常相似,只需将关键字 struct 替换为 union:
union 共用体名
{
数据类型 成员1;
数据类型 成员2;
...
};
声明变量同样延续了结构体的方式:
union 共用体名 变量名;
成员访问也使用点运算符 .:
变量名.成员名
1.3 语法示例
#include <stdio.h>
union Data
{
int i;
float f;
char c;
};
int main(void)
{
union Data d;
d.i = 100;
printf("d.i = %d\n", d.i); // 100
d.f = 3.14f;
printf("d.f = %f\n", d.f); // 3.140000
// 注意:此时 d.i 的值已经无效了,下面不要再读取 d.i
d.c = 'A';
printf("d.c = %c\n", d.c); // A
// 同理,写入 d.c 后,d.f 也不再是当前有效成员
return 0;
}
输出:
d.i = 100
d.f = 3.140000
d.c = A
这段代码没有在写入新成员后继续读取旧成员,因为那样的结果不可移植。它想说明的关键事实是:共用体成员共享同一块空间,写入一个成员后,应当只把最后写入的那个成员视为当前有效成员。
注意:共用体本身并不记录当前存储的是哪一个成员,这一信息需要程序员自行维护——要么凭逻辑记住,要么额外设置一个标记变量来区分当前有效的数据类型。这正是共用体与枚举最自然的结合点:用枚举标记类型,用共用体承载数据,构成**“带标签的联合体”**。下面就来讨论枚举。
二、枚举
1. 枚举(enum)
1.1 从“魔法数字“说起
在之前的程序中,我们经常会使用整数来表示某种状态或选项:
int day = 1; // 1 表示星期一?还是星期日?
if (day == 1) { ... }
这类没有任何说明的裸数字被称为“魔法数字“(magic number)。随着时间的推移,连编写者自己都可能忘记这些数字的含义,更不用说其他人阅读代码时的困惑了。枚举的用途就是为一组相关联的整型常量赋予有意义的名字,让代码的含义一目了然。
1.2 语法基本规则
enum 枚举名
{
标识符1,
标识符2,
标识符3,
...
};
关于枚举值的默认行为,有两条简单规则需要记住:
- 不手动指定:第一个标识符默认值为
0,其后的标识符依次加1 - 手动指定某个值:后续未指定的标识符会在此基础上继续递增
声明枚举变量的语法与结构体类似:
enum 枚举名 变量名;
1.3 语法示例
#include <stdio.h>
// 定义星期枚举
enum Weekday
{
MONDAY, // 0
TUESDAY, // 1
WEDNESDAY, // 2
THURSDAY, // 3
FRIDAY, // 4
SATURDAY, // 5
SUNDAY // 6
};
int main(void)
{
enum Weekday today = WEDNESDAY;
printf("today = %d\n", today); // 2
if (today == WEDNESDAY)
{
printf("今天是星期三\n");
}
return 0;
}
输出:
today = 2
今天是星期三
1.4 手动指定值
枚举的值也可以由程序员手动控制。既可以为所有成员全部指定,也可以只指定其中一部分,其余成员自动递增:
enum Color
{
RED = 1,
GREEN = 5,
BLUE = 10
};
// 或者让部分自动递增
enum ErrorCode
{
OK = 0,
FILE_NOT_FOUND, // 1
PERMISSION_DENIED, // 2
UNKNOWN = 99
};
第一个未被手动指定的成员从 0 开始,其他未指定的成员在前一个已确定成员的值基础上加 1。
1.5 枚举的优点
使用枚举带来的好处体现在三个层面:
- 可读性:
if (today == FRIDAY)远比if (today == 4)清晰,阅读代码的人无需记忆数字与含义的对应关系。 - 组织性:枚举将一组相关的常量集中定义在一起,便于统一管理和修改,不会散落各处。
- 类型提示:枚举是与实现所选整数类型兼容的独立类型。使用枚举变量能表达取值所属的集合;混用不同枚举时,一些编译器也会给出警告。
1.6 枚举与 switch 的经典配合
枚举与 switch 语句是一对天然的搭档。枚举提供命名清晰的常量,switch 则在不同分支中分派对应的处理逻辑:
enum Weekday today = SATURDAY;
switch (today)
{
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
printf("工作日\n");
break;
case SATURDAY:
case SUNDAY:
printf("周末\n");
break;
default:
break;
}
三、动手练习
#include <stdio.h>
// 定义表示图形的共用体
union Shape
{
double radius; // 圆:半径
struct { double w, h; } rect; // 矩形:宽和高
};
// 定义图形类型枚举
enum ShapeType { CIRCLE, RECTANGLE };
int main(void)
{
// 练习1:共用体与枚举配合
enum ShapeType type = CIRCLE;
union Shape s;
if (type == CIRCLE)
{
s.radius = 5.0;
printf("圆形,半径 = %.2f, 面积 = %.2f\n", s.radius, 3.14 * s.radius * s.radius);
}
else if (type == RECTANGLE)
{
s.rect.w = 3.0;
s.rect.h = 4.0;
printf("矩形,宽 = %.2f, 高 = %.2f, 面积 = %.2f\n", s.rect.w, s.rect.h, s.rect.w * s.rect.h);
}
// 练习2:用枚举表示月份,输出月份天数
enum Month { JAN=1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
enum Month m = MAR;
printf("\n%d 月有 ", m);
switch (m)
{
case JAN: case MAR: case MAY: case JUL: case AUG: case OCT: case DEC:
printf("31 天\n"); break;
case APR: case JUN: case SEP: case NOV:
printf("30 天\n"); break;
case FEB:
printf("28 或 29 天\n"); break;
default:
printf("无效月份\n");
}
return 0;
}