文章目录
一、联合体(共同体)1.联合体类型的声明2.联合体的特点测试1测试2 3.联合体大小的计算例1例2 4.联合体小练习5.结构体和联合体内存占用的对比6.联合体的应用 二、枚举1.枚举类型的声明2.枚举类型的优点3.枚举类型的使用
一、联合体(共同体)
1.联合体类型的声明
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以是不同的类型
联合体的特点是所有成员共⽤同⼀块内存空间,所以联合体也叫共同体,由于所有成员共用一块空间,所以编译器只为最⼤的成员分配足够的内存空间 ,并且当给联合体其中⼀个成员赋值时,其他成员的值也跟着变化,我们后面也会讲到
现在我们从联合体类型的声明开始学习,它的声明也和结构体的声明相似,结构体声明时使用struct关键字,而联合体声明时使用union关键字,如下:
union un{char c;int i;};
它创建变量的方式和结构体都是类似的,如下:
union un{char c;int i;};int main(){union un s;return 0;}
其它语法知识点和结构体都是类似的,这里就不再多讲了,接着学习联合体的特点,也可以说是结构体和联合体的区别之处
如果还没有学习结构体,可以参考文章:【C语言】自定义类型:结构体
2.联合体的特点
联合的成员是共用同⼀块内存空间的,这样⼀个联合变量的大小,至少是最⼤成员的大小(因为联合体至少得有能力保存最大的那个成员)
测试1
现在我们来做个测试,测试一下联合体成员的地址是否相同,以及联合体本身的地址和它成员地址的关系,如下代码:
#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;}
接着我们运行它,来看看它们三个地址的关系:
可以看到,联合体成员的地址是相同的,并且联合体本身也和联合体成员的地址相同,基本上就可以说明,联合体开辟空间时,所有成员共用一块空间
测试2
接下来我们再举一个例子来测试联合体的空间是否是共用的,如下:
#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,然后使用它创建了一个联合体变量un,随后将其初始化为了0
然后进行了两次赋值,给i赋值了16进制数11223344,把c赋值为了16进制数55,如果i和c共用相同的空间,那么我们在更改c的时候,i应该也会跟着改变,我们来看看内存里的存储:
这是初始化完i时,结构体内存的存储:
可以看到,这里VS用小端字节序的方式将i存放到了内存中,如果不知道什么是小端字节序,可以参考该文章:【C语言】数据在内存中的存储(万字解析)
现在已经把i存放进内存了,代码下一步就是将c改成16进制数55,那我们看看i会不会跟着一起改变:
可以看到内存中i的第一个字节被修改为了55,说明更改c确实连带着把我们的i更改了,我们可以画一个图来更清楚的阐述这个变化过程:
所以在这个联合体中,我们可以分析得到:整型变量i和字符变量c占据同一块空间,而c占据的就是i的第一个字节,当我们对c进行修改时,也就是对i第一个字节的修改,所以当我们把c修改为0x55时,i也就跟着改变了
我们来看看代码的运行结果:
可以看到,程序的运行结果与我们分析的一致,所以根据这个例子,我们再一次证明了联合体的成员共用一段相同的空间
3.联合体大小的计算
我们首先来看联合体在存储时的两个规则:
联合体的大小至少是最⼤成员的大小,确保联合体的大小可以装下每一个单一成员当最⼤成员大小不是最大对齐数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍例1
还是老方法,实践出真知,我们举一个例子来说明这两条规则:
#include <stdio.h>union Un1{char c[5];int i;};int main(){//下⾯输出的结果是什么?printf("%d\n", sizeof(union Un1));return 0;}
我们首先看第一条规则:联合体的大小至少是最大成员的大小,而我们这里的联合体Un1它最大的成员是c,是一个字符数组,大小是5个字节,所以根据第一条规则,这个联合体的大小至少是5个字节
然后就是第二条规则,这条规则需要查看这个联合体中的最大对齐数,联合体的大小必须是最大对齐数的倍数,而联合体成员c的对齐数为1,成员i的对齐数为4,所以可以知道该联合体最大对齐数为4
根据第一条规则我们知道了联合体Un1的大小至少是5个字节,根据第二条规则我们知道了Un1的大小必须是4的倍数,所以综和这两点,答案已经呼之欲出:结构体Un1的大小是8个字节
最后我们来看看代码运行结果:
例2
#include <stdio.h>union Un2{short c[7];int i;};int main(){//下⾯输出的结果是什么?printf("%d\n", sizeof(union Un2));return 0;}
通过例1,我们已经知道两个规则具体的作用了,现在我们做这个题应该是比较简单的,但是我们还是像例1那样仔细分析一下:
首先根据第一条规则,我们要看成员中,谁最大,很明显最大的就是c数组,占据14个字节,所以联合体Un2至少都有14个字节
然后来看第二个规则,我们要看成员中的最大对齐数,第一个成员c的对齐数是2,第二个成员的对齐数是4,所以联合体Un2的最大对齐数是4,它的大小应该是4的倍数
所以综上两个规则,联合体Un2的大小至少14个字节,还要是4的倍数,所以联合体Un2的大小为16个字节,我们来看看运行结果:
4.联合体小练习
使用联合体写⼀个程序,判断当前机器是大端字节序还是小端字节序
我们先复习一下之前采用的方法,方便我们思考这道题,是创建一个整型变量,赋值为1,然后将它的地址强制转换为字符类型存放起来,然后通过这个字符指针去访问整型变量的第一个字节,看看拿到的是否是1
如果是1说明是小端字节序,是0就是大端字节序,这里再放一下它的代码,如下:
#include <stdio.h>int main(){int a = 1;char* p = (char*)&a;if (*p == 1)printf("小端字节序\n");elseprintf("大端字节序\n");return 0;}
运行结果:
接下来我们就来看如何使用联合体实现这个功能
其实很简单,上面案例的本质就是利用指针来访问整型a的第一个字节,而我们的联合体本身就可以做到这一点
只要在联合体中创建一个整型成员a,创建一个字符型成员c,由于共用空间,那么c就可以直接访问a的第一个字节
接着我们就照着上面那个案例的思路,用联合体实现一下,如下:
#include <stdio.h>union Un{int a;char c;};int main(){union Un un;un.a = 1;if (un.c == 1)printf("小端字节序\n");elseprintf("大端字节序\n");return 0;}
运行结果:
5.结构体和联合体内存占用的对比
我们来简单对比一下同样的成员下,结构体和联合体内存占用的情况,如下例:
struct S{ char c; int i;};union Un{ char c; int i;};
我们来画图看看它们在内存中的占用情况:
可以看到,和结构体对比,联合体非常节省空间,那么联合体改一个成员另一个成员跟着变了,到底该用在什么时候呢?我们继续学习
6.联合体的应用
联合体在使用时可以节省空间,所以我们要学习什么情况下使用联合体,而不是使用结构体
由于它的特性,所以我们应该也能想到它的应用,那就是应用在整个联合体一次性只会出现一个成员的情况下,只要我们在同一时刻只使用一个成员,那么就算把其它成员改变了也没有影响
⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息,如下:
在不思考的情况下,我们可以直接写出以下结构:
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;};
这里我们将这个礼品兑换单整体用结构体存储,其中公共部分就直接当作成员定义进去,其它特殊属性就统一放在一个联合体里面,在联合体里面就把图书、杯子、衬衫分别弄成结构体
在这个联合体里面的成员就是三个结构体,它们共用同一段空间,而在同一时刻我们只会使用其中一个进行描述,所以不会有影响,最后达到了节省空间的目的
二、枚举
1.枚举类型的声明
枚举顾名思义就是⼀⼀列举,可以把所有可能的取值⼀⼀列举出来,⽐如我们现实⽣活中:
⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举性别有:男、⼥,也可以⼀⼀列举三原⾊,也是可以一一列举所以枚举也就是一一列举的意思,而枚举类型的声明和结构体以及联合体的声明相似,但是关键字是enum,接下来我们就来把我们举出的枚举例子一一实现出来,如下:
enum Day//星期{ Mon, Tues, Wed, Thur, Fri, Sat, Sun};enum gender//性别{ MALE, FEMALE,};enum Color//三原色{ RED, GREEN, BLUE};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型,{}中的内容是枚举类型的可能取值,也叫 枚举常量
这些枚举常量都是有值的,默认从0开始,依次递增1,我们可以打印出来看看:
#include <stdio.h>enum color{RED,GREEN,BLUE};int main(){printf("%d %d %d", RED, GREEN, BLUE);return 0;}
运行结果:
那么我们能不能在开始的时候就给它赋值呢?当然可以,如下:
enum Color{RED = 2,GREEN = 4,BLUE = 8};
接着我们再来打印一下它们的值
这就是自定义结构:枚举,里面的成员又叫枚举常量,是无法更改的,一般用来将这些值赋值给其它变量
2.枚举类型的优点
为什么使⽤枚举?我们可以使⽤ #define 定义常量,为什么非要使用枚举来定义枚举常量?我们可以来看看枚举的优点:
增加代码的可读性和可维护性,比如我们想用数字0表示男,数字1表示女,那么写出来就不好理解,如果用枚举中MALE表示男,同时底层代表0,就具有更高的可读性和可维护性和#define定义的标识符比较枚举有类型检查,更加严谨便于调试,预处理阶段会删除 #define 定义的符号,这个在后面的预处理详解我们会讲到使⽤⽅便,⼀次可以定义多个常量枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使用,而#define定义的常量是全局变量所以枚举也是有它自己的优点的
3.枚举类型的使用
在使用枚举时,我们会创建一个枚举变量,然后用枚举类型中的枚举常量给它赋值,如下:
#include <stdio.h>enum Color{RED = 1,GREEN = 2,BLUE = 4};int main(){enum Color clr = GREEN;return 0;}
我们今天要学习的联合体(共同体)以及枚举就到此结束了,是否是有很大收获呢?如果文章内容有误请及时联系我,非常感谢
当然如果有什么关于这篇博客的疑问,可以在评论区或者私信提问,一定会及时回答
那么今天就到这里,bye~