【C语言】自定义类型:联合体和枚举
文章目录
- 一、联合体(共同体)
- 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~