一、联合体
我们在刚开始学C语言的时候学习的都是内置类型,char int long double等类型的数据;上次又学习了自定义类型其中之一的结构体,有了这样的基础,继续学另外一个自定义类型,联合。
1.联合的声明
与结构体实际上在声明(形式)上可以说完全相同。
比如:
联合体的关键字是union,可以看到声明的格式完全一样,联合体是一系列成员组成的,成员的类型可以不用。
2.联合的特点
但是问题就又出来了,既然都单独列为一种自定义类型了,那么肯定不是跟结构体完全相同的,那么区别在哪里呢?
联合的最大特点就是内存分配,编译器只为最大的成员分配足够的内存空间,且也存在对齐的规则。
比如这样的一个例子,如果没学习结构体之前,我们肯定是1 + 4就行了,学了以后是:char第一个元素就顶格,int是4 8比取小的4为对齐数,所以int肯定从4(最近的4的倍数)开始到7,一共开辟了8个字节刚好是1 4中最大的4的倍数,不再补内存空间。
那么接下来运行一下:
一看,4,刚好是int类型的数据的大小,对于这个例子来说,还真的刚好就是这样的,就是取的int的大小。
3.联合的内存分配和对齐
①联合体的内存分配
联合体的内存分配是因为联合的成员是共用同一块内存空间的,这样一个联合体的大小至少是最大的那个成员的所需占内存的大小。
就像我们上面举的例子一样,char类型要占1个字节,而int类型要占4个字节,如果共用一块内存空间应该是4个字节,符合我们输出的结果。
那么这两个元素是怎么存在联合体里的呢,即相对位置是什么样的?
等于根据类型算出来需要多大的内存空间以后,所有的联合体的成员都顶格存呗。
如图所示:
我们学习过整型在内存中存放是分为小端和大端字节序的,而VS中是小端,之前我们写过一个函数来验证:
实际上我们这个什么取a的地址了,然后又为了访问int的第一个字节强转为char*类型去访问,根据存的是1还是0来判断,这个玩意跟我们上面创建的这个联合体其实一样一样的,即代码可以修改为:
对联合体初始化的正是我们对联合体内存分配的理解才能实现刚好完成存储空间的第一个字节为01即1。
②联合体的内存对齐
为什么说至少呢?因为还有对齐的问题,如果根据所占内存最大成员分配不符合对齐的话,那就还需要再分配几个字节的空间。
有了对齐就可以计算一个联合体的大小了:
联合体的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍时,对齐到最大对齐数的整数倍。
比如这样一段代码:
这里强调一个点吧,我们算最大对齐数的时候是根据内置类型来算的,char [5]要根据char来算,数组算是内置类型的堆叠,所以容易得到最大对齐数就是4,然后最大成员就是char [5]这个数组,5个字节肯定不是4的倍数,最后补齐就是8个字节。
类似的有:
这里直接放出答案了,自己算一算吧。
总体比结构体稍微简单一点,直接找最大的然后找最大对齐数就行,找最大的一步就行,最大对齐数得一个一个算;结构体除了第一个每个都得算对齐数,然后去找最近的偏移量的倍数,最后还得汇总起来这些对齐数找个最大对齐数看看最后算出来的够不够最大对齐数的整数倍。
4.联合体的优点
对比下来就是省空间,但是不可避免的问题就是数据一旦修改就会影响联合体的其它成员的值,有这样的场景:
将联合体看成一个背包的话,那么就应该是这样的:
一个背包就二十个int的大小,如果你要放食物,那水和书就不能放,放了就得把水和书扔出来。
那这样的话为了省内存导致数据被修改岂不是得不偿失了嘛。
上面这个只是展示一下数据一旦被修改就会影响结构体其他的成员的值。
正儿八经就像这样:
桌子的大小是一定的,所以在一个时间段就只能干一个事,这个时候创建个联合体的话那不就省了空间又完成了任务,当然,我只是简单举个例子,真实场景不一定这样,只是方便理解。
二、枚举类型
枚举类型就是把某些有限取值一一列举出来。
我们生活中有很多这样的例子:
一年有12个月
一周有七天
性别分男女等
这些其实都可以说是不变的常量,在这种情况下如果我们想要实现,最简单的方式就是枚举。
1.枚举类型的声明
枚举类型的关键字是enum,具体使用是这样的:
这些的所有可能取值都是有值的,如果不赋值的话,默认从0开始依次递增1:
如果赋值的话,还是依次+1:
2.枚举类型的优点
有些值我们想存储到计算机中,比如我们上面声明中举例的星期,一共有七个值,这还是我们写简写的情况,你说存到哪,存到字符数组吗?字符数组也是以一个个字母为单位的;存成1,2,3,4……吗,谁知道你写的啥意思,在我们之前讲perror函数和strerror函数时,errno变量里每一个数字都对应着一种错误类型,除了0,在布尔类型中又是ture。
所以以前我们其实没办法对这种有实际意义的量有很好的存储方式。
①存储方便。
②增加了代码的可读性和可维护性,不管是谁,一看就知道这些具体数字是什么意思。
③这种自定义类型创建的枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。
3.枚举类型的使用
最简单的有: