引言
在上一篇关于结构体的文章中我们知道,C语言除了提供了内置类型,还提供了了自定义类型,今天我们要学习的便是自定义类型中的联合体类型和枚举类型
联合体
1.联合体的定义
联合体是一种特殊的数据结构,允许我们在相同的内存位置存储不同的数据类型
和结构体一样,联合体也是由一个或者多个成员构成的,这些成员可以是不同的类型
但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间
因此联合体也叫共用体
当我们给联合体的其中一个成员赋值时,其他成员的值也会发生改变
2.联合体的声明
联合体的结构类似于结构体,由关键字union和多个成员变量组成。格式如下:
union union_name {
data_type1 member1;
data_type2 member2;
// ...
data_typen membern;
};
其中:
- union_name 是联合体的名称
- data_type1, data_type2, ..., data_typen 是联合体中各成员的数据类型
- member1, member2, ..., membern 是联合体中的成员名称
2.1 普通联合体
union data
{
char c;
int i;
};
2.2 嵌套联合体
// 定义一个内嵌的联合体类型
union InnerUnion
{
int x;
float y;
};
// 定义一个包含内嵌联合体类型的外部联合体
union OuterUnion
{
char c;
union InnerUnion inner; //这里嵌套了InnerUnion
};
2.3 匿名联合体
匿名联合体允许在不定义联合体名称的情况下,创建一个联合体对象,这种联合体只能在其定义的代码块内使用一次
union
{
char c;
int i;
};
2.4 用typedef简化联合体
我们可以使用typedef简化联合体,减少代码量
typedef union Un1
{
char c;
int i;
}Un1;//这样子我们可以使用Un1代替union Un1
3.联合体的创建以及初始化
下面的代码是联合体的创建以及初始化:
union Un
{
char c;
int i;
};
int main()
{
//联合体的初始化
union Un un = { 0 };
return 0;
}
ps:由于联合体的成员是共用同一块内存空间的,因此它的初始化不同于结构体,联合体的初始化只需要一个值
4.联合体的访问
我们使用点操作符(.),如下所示:
union Un
{
int i;
char c;
}un;
int main()
{
union Un un = { 0 };
un.c = 'c';
printf("%c\n", un.c);
un.i = 114514;
printf("%d\n", un.i);
return 0;
}
输出结果为:
c
114514
由于联合体的所有成员共用同一块内存空间,当我们对上面的代码进行调整:
union Un
{
int i;
char c;
}un;
int main()
{
union Un un = { 0 };
un.c = 'c';
un.i = 114514;
printf("%c\n", un.c);
printf("%d\n", un.i);
return 0;
}
这样子输出的结果可能就不是我们想要得到的结果
5.联合体的内存存储
5.1 联合体的大小
联合体的大小取决于其成员中最大的数据类型的大小。这是因为联合体中的所有成员都共享同一块内存空间,所以联合体的大小至少需要足够大以容纳其最大的成员
union Un
{
int i;
char c;
};
int main() {
printf("%zu\n", sizeof(union Un));
return 0;
}
输出结果为:
4
联合体大小的计算有如下的规律:
1.联合体的大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍
5.2 联合体的存储
了解了联合体的大小,接下来我们来了解一下联合体的存储
示例1
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;
}
输出结果如下:
通过观察我们可以得知:联合体的存储是从起始位置开始共用的
示例2
代码如下:
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;
}
输出结果如下:
11223355
在我之前的文章《C语言——数据在内存中的存储》,我们了解了计算机有关大小端的知识
在VS2022这一编译器中,数据在内存中的存储为小端存储
黄色部分为共有部分
绿色部分为非共有部分
示例3
代码如下:
union Un1
{
char c;
int i;
};
int main()
{
printf("大小为%zu\n", sizeof(union Un1));
return 0;
}
输出结果为:
大小为4
示例4
代码如下:
union Un
{
short c[5];
int i;
};
int main()
{
printf("大小为%zu\n", sizeof(union Un));
return 0;
}
输出结果为:
大小为12
- short大小为2,c中有5个大小为10,i大小为4,共用四个字节
- 最大对齐数为4,联合体大小为最大对齐数的整数倍,为12
6.联合体的使用示例
6.1 使用联合体判断大小端
我们之前学习数据在内存中的存储时,已经学习了一种可以判断大小端的方法,现在我们学习了联合体之后,可以使用联合体判断大小端
我们可以根据联合体的特性:联合体成员存储的起始位置相同 来实现取出第一位这一需求
代码如下:
int check_sys()
{
union
{
char c;
int i;
}u;
u.i = 1;
return u.c;
}
//如果是小端字节序 对i赋值时内存中是0x01 00 00 00
//读取c时读取前两个字节,即01
//如果是大端字节序 对i赋值时内存中是0x00 00 00 01
//读取c时读取前两个字节,即00
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
6.2 联合体的实际应用
使用联合体是可以节省空间的,例如:
当我们要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三种礼品:图书、杯子、衬衫
每一种礼品都有:库存量、价格、商品类型以及其他信息
图书:书名、作者、页数
被子:设计
衬衫:设计、可选颜色、可选尺寸
这一看,我们似乎可以使用结构体:
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; //尺⼨
};
这样的结构设计的很简单,用起来也十分方便,但是结构的设计中包含了所有礼品的各种属性,这样子会导致结构体的大小会偏大,比较浪费内存
因为对于礼品兑换单中的商品来说,只有部分属性信息时常用的,例如:礼品是图书时,就不需要design、colors、sizes
因此我们就可以把公共元素单独写出来,剩下属于各种商品本身的属性使用联合体起来,这样就可以介绍所需的内存空间,一定程度上节省了内存
struct gift_list
{
int stock_number; //库存量
double price; //定价
int item_type; //商品类型
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;
};
枚举类型
C语言中的枚举类型是一种用户定义的数据类型,它允许你为整数值指定一个名称,从而增加代码的可读性。枚举类型通常用于表示一组固定的、命名的整数常量
7.枚举的定义
枚举类型通过关键字 enum来定义
在实际应用中我们经常把能够且便于一一列举的类型用枚举来表示,就比如一周的星期,一年的月份......
enum 枚举类型名
{
枚举常量1,
枚举常量2,
...
枚举常量n
};
8.枚举的声明
8.1 普通枚举
举个例子,我们可以使用枚举将一周的星期全部列出来
代码如下:
enum DAY
{
MON=1, //指定从1开始,否则默认从0开始
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
8.2 匿名枚举
与匿名结构体和匿名联合体类似,枚举也有匿名枚举
例如这样:
enum
{
zhangsan,
lisi,
wangwu
};
8.3 用typedef简化枚举类型
我们可以使用typedef简化枚举类型,减少代码量
如下所示:
typedef enum DAY
{
MON = 1, //指定从1开始,否则默认从0开始
TUE,
WED,
THU,
FRI,
SAT,
SUN
}DAY;
9.枚举的创建和初始化
我们可以利用定义的枚举常量对枚举变量进行赋值
代码如下:
typedef enum Color
{
RED,
GREEN,
BLUE,
YELLOW
}Color;
int main()
{
for (int i = RED; i < YELLOW; i++)
{
printf("%d ", i);
}
return 0;
}
10.枚举常量的打印
我们来看一段代码:
typedef enum Color
{
RED,
GREEN,
BLUE,
YELLOW
}Color;
int main()
{
for (int i = RED; i <= YELLOW; i++)
{
printf("%d ", i);
}
return 0;
}
输出结果为:
0 1 2 3
观察结果我们可以知道:枚举成员是个常量,默认从零开始
11.枚举常量的大小
枚举常量在计算机中所占字节数是多少呢?
我们可以通过代码来查看一下:
typedef enum Color
{
RED,
GREEN,
BLUE,
YELLOW
}Color;
int main()
{
printf("enum color: %d\n", sizeof(Color));
return 0;
}
输出结果为:
enum color: 4
枚举类型的大小与int类型一样,都是4个字节
12.枚举类型的优点
我们可以使用 #define 定义常量,为什么要使用枚举
枚举的优点有:
1.增加代码的可读性和可维护性
2.和 #define 定义的标识符比较枚举有类型检查,更加严谨
3.便于调试,预处理阶段会删除 #define 定义的符号
4.使用方便,一次可以定义多个常量
5.枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用
13.枚举类型的使用示例
enum Color
{
RED = 1,
GREEN = 2,
BLUE = 4
};
int main()
{
enum Color clr = GREEN;
printf("The color is: %d\n", clr);
return 0;
}
这样子可以实验使用枚举常量给枚举变量赋值
结束语
磨磨蹭蹭写了很久,终于是写完了
希望看到的朋友们能点赞收藏加关注!!!