目录
一、联合体
1.1联合体类型的声明
联合体的声明和结构体一样,只是名字的不同,联合体是union,例如:
cpp
union U
{
// 成员共用同一块空间
int a;
char b;
float c;
};
1.2联合体的特点
联合体的所有成员共用一块内存空间,修改一个成员,会覆盖掉其他成员,联合变量的大小,至少是最大成员的大小,因为要保证至少能放得下最大的那个成员
cpp
#include <stdio.h>
// 联合类型的声明
union Un
{
char c; // 1字节
int i; // 4字节
};
int main()
{
// 联合变量的定义
union Un un = {0};
// 计算联合体变量的大小
printf("%d\n", sizeof(un));
return 0;
}
联合体union Un的成员中最大的成员int占4个字节,所以我们会至少开辟4个字节的空间给这个联合体
运行截图:
1.3联合体的内存布局
联合体在内存上的存储布局到底是怎样的呢?我们来看这一串代码:
cpp
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = { 0 };
// 下⾯输出的结果是⼀样的吗?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
return 0;
}
运行截图:

可以看到 un.i 和 un.c 它们的地址是相同的,这说明它们的起始地址都一样,它们共用同一块内存空间,&un 取出的就是整个联合体的地址,数值上就是整个联合体的起始地址
我们再来看一段代码,从联合体中对成员的修改来分析联合体的内存布局:
cpp
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = { 0 };
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
我们来分析一下这串代码,假设我们的联合体成员是共用同一块内存空间的,un.i = 0x11223344,在VS中数据是以小端模式存储的,那么un.i在内存中存储的就是44332211,此时我们将un.c修改为0x55,c为char类型,修改也就是访问一个字节,如果它们共用同一块内存,那么将会将此联合体开辟空间的第一个字节的内容覆盖为0x55,会打印出11223355
我们来验证一下以上的分析是否正确,运行截图:

1.4联合体大小的计算
计算规则:
1.联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
cpp
#include <stdio.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
//下⾯输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
Un1中:最大的成员大小为char c5,对齐数是1,占5个字节的大小,所以我们至少开辟了5个字节的空间,另外一个成员int i的对齐数是4,所以它们的最大对齐数是4,5个字节不是4的倍数,所以我们又开辟3个字节的空间,总共就开辟了8个字节的空间,8是最大对齐数4的倍数,符合规则printf("%d\n", sizeof(union Un1))的结果就为8
Un2中:最大的成员大小为short c7,对齐数是2,占14个字节的大小,所以我们至少开辟了14个字节的空间,另外一个成员int i的对齐数是4,所以它们的最大对齐数是4,14不是4的倍数,所以我们又开辟了两个字节的空间,总共就开辟了16个字节的空间,16是最大对齐数4的倍数,符合规则,printf("%d\n", sizeof(union Un2))的结果就为16
运行截图:

1.5使用联合体节省空间
如果我们想描述一种礼物,这个礼物可以是几类商品,例如图书、衣服等,它们共同拥有的性质是商品类型、库存量、价格,图书还拥有:书名、作者、页数;衣服还拥有:设计、可选颜色、可选尺寸
如果只用结构体去描述:
cpp
struct gift_list
{
//公共属性
int stock_number;//库存量
double price; //定价
int item_type; //商品类型
//特殊属性
char title[20]; //书名
char author[20]; //作者
int num_pages; //页数
char design[30]; //设计
int colors; //颜色
int sizes; //尺寸
};
结构体是将所有这些信息都罗列出来,这样做的话就比较浪费空间,因为当我们想描述书本时,我们就不需要描述设计,颜色,尺寸,但这三个信息依然占用空间;而当我们想描述衣服时,我们也不需要描述书名,作者,页数,但这三个信息依然占用空间
我们来看看这个结构体具体占多少个字节的空间:
cpp
int main()
{
printf("%zu\n", sizeof(struct gift_list));
return 0;
}
运行截图:

如图所示,这个结构体大小占104个字节的空间
如果我们会采用联合体去改进:
cpp
// 礼品商品结构体
struct gift_list
{
// ====================== 公共属性(所有商品都具备)======================
int stock_number; // 库存量
double price; // 价格
int item_type; // 商品类型标记:1=图书 2=衣服
// ====================== 特殊属性(互斥使用,用联合体节省空间)======================
// 联合体:同一时间只能存放一种商品的信息,共用同一块内存空间
union p {
//图书
struct {
char title[20]; // 书名
char author[20]; // 作者
int num_pages; // 页数
} book;
// 衣服
struct {
char design[30]; // 设计图案
int colors; // 可选颜色数量
int sizes; // 可选尺码数量
} shirt;
} item; // 联合体变量名:用于访问具体商品的属性
};
我们来看看用联合体改进后,结构体所占内存空间是多大:
cpp
int main()
{
printf("%zd\n", sizeof(union p));
printf("%zd\n", sizeof(struct gift_list));
return 0;
}
运行截图:

只占用了64个字节的空间,所以用union联合体改进后,确实在一定程序上减少了内存的浪费
1.6联合体判断大小端
cpp
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;//返回1是⼩端,返回0是⼤端
}
因为联合体union中int i 和char c 共用同一块内存空间,开辟了四个字节的空间,将un.i=1,也就会将这四个字节的空间都使用,如果是大端存储的话,i中存的就是0x00000001,如果是小端存储的话,i中存的就是0x01000000,所以当我们返回un.c时,其实就是访问我们的第一个字节的内容,大端就会返回0x00,也就是char类型的0,小端就会返回0x01,也就是char类型的1
二、枚举类型
2.1枚举类型的声明
枚举类型,就是将一个事物所有可能的取值,都一一列举进去,枚举类型中存的是枚举常量,枚举常量是有数值的,默认从0开始,依次递增1
枚举类型的语法:
cpp
enum 枚举名
{
枚举常量1, // 默认从 0 开始
枚举常量2, // 1
枚举常量3 // 2
}; //注意分号不能丢!!!
enum + 枚举名 构成一个枚举类型,可以把一个事物可能的取值一一列举进去,比如:一个商品类型中,可以有书,有衣服,有杯子,我们就可以这样去表示:
cpp
// 声明一个枚举类型:商品类型
enum ItemType
{
BOOK, // 0 书
CLOTHES, // 1 衣服
MUG // 2 杯子
}
这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值
比如:
cpp
#include <stdio.h>
// 声明一个枚举类型:商品类型
enum ItemType
{
BOOK , // 0 书
CLOTHES = 9, // 9 衣服
MUG // 10 杯子
};
int main()
{
enum ItemType item1= BOOK;
enum ItemType item2= CLOTHES;
enum ItemType item3= MUG;
printf("%d\n", item1);
printf("%d\n", item2);
printf("%d\n", item3);
return 0;
}
正常来说,枚举类型成员的取值是默认从0开始的,但如果我们将中途一个枚举常量的值修改,那么余下的枚举常量的取值也会随之修改,后面的枚举常量会接着这个数继续往下一直 +1,比如上述代码中,我们将枚举常量CLOTHES的值修改为9,那么后面的取值将会从9开始递增,MUG的值就会变成9+1=10
运行截图:

2.2枚举类型变量的创建
比如枚举商品类型:
cpp
// 声明一个枚举类型:商品类型
enum ItemType
{
BOOK, // 0 书
CLOTHES, // 1 衣服
MUG // 2 杯子
}item1, item2; //全局枚举变量
enum ItemType item3; //全局枚举变量
int main()
{
enum ItemType item4; // 局部枚举变量
return 0;
}
我们可以像结构体一样可以在声明的时候就定义好变量,如代码中的item1 和 item2
也可以单独创建变量,如在main函数外创建的全局枚举变量item3 和 在main函数内创建局部枚举变量item4
2.3枚举类型的优点
#define 也可以定义常变量,那为什么我们要使用枚举呢?
枚举的优点:
**1.**枚举可以一次性定义多个常量,但#define一次性只能定义一个常量,书写较为繁琐
**2.**代码可读性更强,一眼就能看懂代码的意思
3.#define定义常变量是纯替换文本,没有类型检查,但枚举有类型检查,比如:
cpp
#define BOOK 1
#define CLOTHES 2
#define MUG 3
int type; //商品类型
type = 100;
用#define定义时,我们想创建一个表示商品类型的变量,当我们将不属于我们有的商品类型的数值赋值给type时,编译器不会报错,无类型检查,但如果是在枚举类型中,在我们的C++中,就会报错,例如以下代码:
cpp
#include <stdio.h>
enum ItemType
{
BOOK,
CLOTHES,
MUG
};
int main()
{
enum ItemType item = 100;
}
编译器会报错:
**4.**枚举类型更便于调试,#define在预处理阶段会删除 #define 定义的符号
cpp
#define Max 100
#include <stdio.h>
int main()
{
int i = Max;
return 0;
}

可以看到在调试时,我们的Max已经被替换成数字100了,我们调试时看不到这个100代表的含义,#define 定义的符号已经被删除,只能知道它的值是100
cpp
#include <stdio.h>
enum ItemType
{
BOOK,
CLOTHES,
MUG
};
int main()
{
enum ItemType item=BOOK;
}

可以看到,在调试的时候,依然能看到item的值是BOOK,它代表的含义就是书,含义清晰,更加方便调试,让调试者知道这个值代表的含义,而不是单纯一串数字,其他人看到后会蒙
使用枚举时,调试器可以直接显示变量的值是 BOOK,我们一眼就能看懂它代表 "书",含义非常清晰。 而 #define 定义的常量在预处理阶段就被替换成了数字,调试时只能看到 0、1 这种冷冰冰的数字,不知道含义,让人看得发蒙。
所以,枚举大大方便了调试,代码可读性更强
**5.**枚举是受作用域限制的,枚举声明在函数内,就只能在函数内使用,更加安全; #define 没有作用域,在整个程序中通用,容易产生冲突。
就比如你定义 #define BOOK 0,别人也定义 #define BOOK 100,后面的直接覆盖前面的,程序莫名其妙出错,还找不到原因
总结:枚举类型的五大优点
**1.**枚举可以一次性定义多个常量,但#define一次性只能定义一个常量,书写较为繁琐
**2.**代码可读性更强,一眼就能看懂代码的意思
3.#define定义常变量是纯替换文本,没有类型检查,但枚举有类型检查
**4.**枚举类型更便于调试,#define在预处理阶段会删除 #define 定义的符号
**5.**枚举是受作用域限制的,枚举声明在函数内,就只能在函数内使用,更加安全; #define 没有作用域,在整个程序中通用,容易产生冲突。
2.4枚举类型的使用
枚举类型一般和switch一起使用,下面我们来看看具体使用方法:
cpp
#include <stdio.h>
// 1. 定义枚举
typedef enum {
BOOK,
CLOTHES,
MUG
} ItemType;
// 2. 你写的函数
void printType(ItemType t)
{
switch (t)
{
case BOOK: printf("书籍\n"); break;
case CLOTHES: printf("衣服\n"); break;
case MUG: printf("杯子\n"); break;
}
}
// 3. 主函数使用
int main()
{
ItemType a = BOOK; // 变量 a 是书
ItemType b = CLOTHES;// 变量 b 是衣服
printType(a); // 打印 书
printType(b); // 打印 衣服
printType(MUG); // 打印 书
return 0;
}
当我们有了枚举类型之后,我们在switch语句中用case语句时,我们就不用用数字0,1,2来表示我们所想表示的事物,比如BOOK,CLOTHES等,但有了枚举之后,我们就可以直接写case BOOK,case CLOTHES这种代码清晰易懂,一目了然,不会像数字一样让人懵,这也是C 语言枚举存在最主要的目的
运行截图:
感谢大家的观看,新人求互三,关注我必回关!下章见!
