下面代码结果为什么这样:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
union un
{
char c;
int i;
};
int main()
{
union un u= { 0 };
printf("%zd\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &u.c);
printf("%p\n", &u.i);
return 0;
}

一、联合体(Union):内存共用的 "空间魔法师"
联合体,也叫共用体,核心特性是所有成员共享同一块内存空间。这和结构体(成员各自占用独立内存)形成了鲜明对比,也是它节省内存的关键所在。
1. 联合体类型的声明
联合体的声明语法和结构体非常相似,仅关键字不同(union 替代 struct),常见三种声明方式:
cpp
#include <stdio.h>
// 1. 基本声明:先定义类型,再定义变量
union Un {
char c; // 字符型成员
int i; // 整型成员
};
// 2. 声明同时定义变量
union Un2 {
short s;
long l;
} un2_var;
// 3. 匿名联合体:无类型名,仅能声明时定义变量
union {
float f;
int arr[2];
} anon_un;
int main() {
union Un un = {0}; // 定义联合体变量并初始化
printf("联合体Un的大小:%d\n", sizeof(un)); // 输出结果:4
return 0;
}
为什么 union Un 的大小是 4?答案藏在联合体的核心特点里。
2. 联合体的核心特点
(1)所有成员共享同一块内存
联合体变量的起始地址,与每个成员的起始地址完全相同。我们用代码验证:
cpp
union Un un = {0};
printf("&un = %p\n", &un);
printf("&un.i = %p\n", &(un.i));
printf("&un.c = %p\n", &(un.c));
(2)修改一个成员,会覆盖其他成员
由于内存共用,对一个成员赋值后,其他成员的值会被 "冲掉"。看下面的例子:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
union un
{
char c;
int i;
};
int main()
{
union un u= { 0 };
printf("%zd\n", sizeof(u));
u.i=0x12345678;
u.c = 0x55;//55转换十进制为85,即'U'
return 0;
}
visual studio采用小端存贮,调试查看变量内存:


改变c的时候,i的值也发生了变化,因为他们共有内存
(3)与结构体的内存布局对比
用相同的成员分别定义结构体和联合体,内存占用差异明显:
cpp
#include <stdio.h>
// 结构体:成员独立占用内存(含内存对齐)
struct S {
char c;
int i;
};
// 联合体:成员共享内存
union Un {
char c;
int i;
};
int main() {
printf("struct S的大小:%d\n", sizeof(struct S)); // 输出:8(含3字节对齐填充)
printf("union Un的大小:%d\n", sizeof(union Un)); // 输出:4(占最大成员大小)
return 0;
}

结构体的内存布局会有对齐填充(浪费部分空间),而联合体完全复用内存,空间效率更高。
3. 联合体大小的计算
联合体的大小遵循两个规则,缺一不可:
- 联合体的大小 至少是最大成员的大小(必须能容纳最大的成员);
- 若最大成员大小不是 "最大对齐数" 的整数倍,需对齐到最大对齐数的整数倍。
对齐数:每个成员的对齐数通常等于其自身大小(如 char 对齐数 1、int 对齐数 4、short 对齐数 2,编译器可能有差异)
cpp
// 示例1:union Un1
#include <stdio.h>
union Un1 {
char c[5]; // 大小5,对齐数1
int i; // 大小4,对齐数4
};
// 最大成员大小5,最大对齐数4;5不是4的整数倍,需补3字节→总大小8
// 示例2:union Un2
union Un2 {
short c[7]; // 大小14(7×2),short 对齐数2
int i; // 大小4,int 对齐数4
};
// 最大成员大小14,最大对齐数4;14不是4的整数倍,需补2字节→总大小16
int main() {
printf("sizeof(Un1) = %d\n", sizeof(union Un1)); // 输出:8
printf("sizeof(Un2) = %d\n", sizeof(union Un2)); // 输出:16
return 0;
}
4. 联合体的实用场景
(1)节省内存:多类型属性复用空间
想象一个礼品店有 3 种商品:图书、杯子、衬衫。它们有共同的属性(库存、价格、类型),但也有各自独有的属性(比如图书有书名 / 作者,衬衫有颜色 / 尺寸)。
如果为每种商品单独建一个结构体,会浪费内存;用「结构体 + 联合体」的方式,就能让不同商品的专属属性共享同一块内存空间,大大节省内存。:
cpp
struct gift_list {
int stock_number; // 公共属性:库存量
double price; // 公共属性:价格
int item_type; // 公共属性:商品类型(1=图书,2=杯子,3=衬衫)
// 专属属性用联合体复用内存
union {
struct {
char title[20]; // 书名
char author[20]; // 作者
int num_pages; // 页数
} book;
struct {
char design[30]; // 设计方案
} mug;
struct {
char design[30]; // 设计方案
int colors; // 颜色
int sizes; // 尺寸
} shirt;
} item;
};
这样一来,专属属性只占用最大成员(shirt)的内存,大幅节省空间
cpp
#include<stdio.h>
// 引入字符串操作头文件,用于strcpy/strncpy
#include<string.h>
struct gift_list {
int stock_number; // 公共属性:库存量
double price; // 公共属性:价格
int item_type; // 公共属性:商品类型(1=图书,2=杯子,3=衬衫)
// 专属属性用联合体复用内存
union {
struct {
char title[20]; // 书名
char author[20]; // 作者
int num_pages; // 页数
} book;
struct {
char design[30]; // 设计方案
} mug;
struct {
char design[30]; // 设计方案
int colors; // 颜色
int sizes; // 尺寸
} shirt;
} item;
};
int main()
{
// 1. 定义一个礼品变量(图书类型)
struct gift_list gift1;
// 2. 给公共属性赋值
gift1.stock_number = 88; // 库存88本
gift1.price = 69.9; // 价格69.9元
gift1.item_type = 1; // 标记为图书类型
// 3. 给图书专属属性赋值(因为item_type=1,所以操作book成员)
// 使用strncpy避免字符串超长导致内存越界,最后一位留空字符
strncpy(gift1.item.book.title, "C语言从入门到精通", 19);
gift1.item.book.title[19] = '\0'; // 确保字符串以'\0'结尾
strncpy(gift1.item.book.author, "王编程", 19);
gift1.item.book.author[19] = '\0';
gift1.item.book.num_pages = 450; // 页数450页
// 4. 打印输出gift1的所有信息,验证赋值是否成功
printf("===== 图书礼品信息 =====\n");
printf("库存量:%d\n", gift1.stock_number);
printf("价格:%.2f 元\n", gift1.price);
printf("商品类型:%d(图书)\n", gift1.item_type);
printf("书名:%s\n", gift1.item.book.title);
printf("作者:%s\n", gift1.item.book.author);
printf("页数:%d 页\n", gift1.item.book.num_pages);
printf("========================\n\n");
// 【拓展】再演示一个衬衫类型的礼品赋值和打印
struct gift_list gift2;
// 公共属性
gift2.stock_number = 50;
gift2.price = 139.9;
gift2.item_type = 3; // 标记为衬衫
// 衬衫专属属性
strncpy(gift2.item.shirt.design, "纯棉格子款", 29);
gift2.item.shirt.design[29] = '\0';
gift2.item.shirt.colors = 4; // 4种颜色
gift2.item.shirt.sizes = 5; // 5种尺寸
// 打印衬衫信息
printf("===== 衬衫礼品信息 =====\n");
printf("库存量:%d\n", gift2.stock_number);
printf("价格:%.2f 元\n", gift2.price);
printf("商品类型:%d(衬衫)\n", gift2.item_type);
printf("设计方案:%s\n", gift2.item.shirt.design);
printf("颜色数量:%d 种\n", gift2.item.shirt.colors);
printf("尺寸数量:%d 种\n", gift2.item.shirt.sizes);
printf("========================\n");
return 0;
}
(2)判断机器字节序(大端 / 小端)
这是联合体的经典用法!字节序指多字节数据在内存中的存储顺序:
- 小端:低字节存低地址;
- 大端:低字节存高地址。
用联合体实现判断函数:
cpp
// 返回1:小端;返回0:大端
int check_sys() {
union {
int i; // 4字节
char c; // 1字节(仅占用i的低地址字节)
} un;
un.i = 1; // 十六进制:0x00000001
return un.c; // 若c=1→小端(低字节1存在低地址);若c=0→大端
}
int main() {
if (check_sys() == 1) {
printf("当前机器是小端存储\n");
}
else {
printf("当前机器是大端存储\n");
}
return 0;
}
二、枚举(Enum):优雅的 "常量集合"
枚举,顾名思义就是 "一 一列举",用于定义一组离散的命名常量(如星期、性别、状态码),让代码更具可读性。
1. 枚举类型的声明
枚举的关键字是 enum,语法格式如下:
cpp
// 基本声明:默认枚举常量从0开始递增
enum Day { // 枚举类型:星期
Mon, // 0
Tues, // 1
Wed, // 2
Thur, // 3
Fri, // 4
Sat, // 5
Sun // 6
};
// 声明时指定枚举常量的值
enum Color {
RED = 2, // 手动指定为2
GREEN = 4, // 手动指定为4
BLUE = 8 // 手动指定为8
};
// 声明同时定义变量
enum Sex {
MALE, // 0
FEMALE, // 1
SECRET // 2
} person_sex;
注意:枚举常量是整型常量,默认从 0 开始,依次递增 1;若手动指定某个常量的值,后续未指定的常量会在前一个基础上递增 1
2. 枚举的核心优点
为什么不用 #define 定义常量,非要用枚举?枚举的 5 大优势:
- 可读性更强 :
RED比2更直观,一眼能明白含义; - 类型安全 :枚举变量只能赋值为对应的枚举常量,
#define无类型检查(比如#define RED 2后,可给任意整型变量赋值 2,无法限制范围); - 便于调试 :预处理阶段会删除
#define定义的符号,调试时只能看到数值(名字被去掉,只有数值给变量);而枚举常量会保留名称,调试更清晰; - 批量定义 :一次可定义多个常量,无需重复写
#define; - 遵循作用域规则:若枚举声明在函数内,仅能在该函数内使用,避免全局污染。
例子:
cpp
#include<stdio.h>
// 定义枚举类型:计算器操作选项(EXIT=0, ADD=1, SUB=2, MUL=3, DIV=4)
enum option
{
EXIT, // 退出
ADD, // 加法
SUB, // 减法
MUL, // 乘法
DIV // 除法
};
int main() {
enum option choice; // 定义枚举变量存储用户选择
double a, b;
while (1) {
// 1. 显示简单菜单
printf("\n简易计算器:\n");
printf("%d-退出 | %d-加法 | %d-减法 | %d-乘法 | %d-除法\n", EXIT, ADD, SUB, MUL, DIV);
printf("请选择:");
scanf("%d", (int*)&choice); // 枚举本质是整型,强转地址
// 2. 退出逻辑
if (choice == EXIT) {
printf("退出程序!\n");
break;
}
// 3. 输入运算数
printf("输入两个数(空格分隔):");
scanf("%lf %lf", &a, &b);
// 4. 枚举配合switch执行运算(核心用法)
switch (choice) {
case ADD: printf("结果:%.2f\n", a + b); break;
case SUB: printf("结果:%.2f\n", a - b); break;
case MUL: printf("结果:%.2f\n", a * b); break;
case DIV:
if (b == 0) printf("除数不能为0!\n");
else printf("结果:%.2f\n", a / b);
break;
default: printf("选择错误!\n");
}
}
return 0;
}
(1)给枚举变量赋值
枚举变量只能赋值为对应的枚举常量,C 语言允许整型隐式转换(不推荐),C++ 则严格禁止:
cpp
enum Color {
RED = 1,
GREEN = 2,
BLUE = 4
};
int main() {
enum Color clr1 = GREEN; // 合法:用枚举常量赋值
enum Color clr2 = 2; // C语言允许(不推荐),C++报错
enum Color clr3 = (enum Color)4; // 显式转换,更规范
printf("clr1 = %d\n", clr1); // 输出:2
return 0;
}