1、位段
1.1、什么是位段
- 位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是 int、unsigned int 或signed int 。
- 位段的成员名后边有一个冒号和一个数字。
- 比如:
#include<stdio.h> struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; int main() { printf("%d\n", sizeof(struct A)); //8 return 0; }
A就是一个位段类型。那位段A的大小是多少?
解析:
上述代码中,_a : 2 表示 _a 只需要2个比特位,_b : 5 表示 _b 只需要5个比特位,_c : 10表示_c只需要10个比特位,_d : 30表示 _d 只需要30个比特位。将这些比特位全部+起来总共47个bit位,何来8字节呢?需要了解下面的位段内存分配:
1.2、位段的内存分配
位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
综上所述:
_a是int类型的,先开辟4个byte,_a占2个bit,还剩30bit,_b占5个bit,还剩25bit,_c占10个bit,还剩15bit,15个bit的大小不够_d的30bit大小,且_d还是int类型的,所以再次开辟4个byte,装下了_d的大小,所以位段A的大小8字节。
再比如:
#include<stdio.h> struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main() { printf("%d\n", sizeof(struct S));//3 struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0; }
此时a是char类型的,先开辟1个字节,a占了3个bit,还剩5个,b占了4个bit,还剩1个bit,不够c的5个bit,再开辟1个字节,赋给 c 5个bit位,还剩3个,又不够给d了,再开辟1个字节足矣。综上此时开辟3个字节的大小。此情况是存在空间浪费的。那1个bit位被浪费了,但是没办法。
位段在一定程度上可以节省空间,但也会适当浪费空间(很小)。
画图解释具体空间开辟情况:为了证明图中的结论,在VS编译器打开监视看看内存:1.3、位段的跨平台问题
位段本身是不支持跨平台的,原因如下:
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16bit,32位机器最大32bit,写成27bit,在16位机器会出问题)
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
1.4、位段的应用 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,即使有跨平台的问题存在。
2、枚举
枚举顾名思义就是一一列举。把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
这里就可以使用枚举了。
注意:枚举类型的大小始终4字节
2.1、枚举类型的定义
#include<stdio.h> enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; int main() { printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun); return 0; }
以上定义的 enum Day,是枚举类型。{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。 从运行结果看,不难发现枚举是有初始值的,默认第一个枚举常量为0,向下依次+1。当然在定义的时候也可以赋初值。
- 例如:
#include<stdio.h> enum Day//星期 { Mon = 1, Tues, Wed, Thur = 3, Fri = 8, Sat, Sun = 0 }; int main() { printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun); return 0; }
2.2、枚举的优点
#define MALE 4 #define FEMALE 5 #define SECRET 6 typedef enum Sex { MALE=4, FEMALE, SECRET }Sex;
- 我们可以使用 #define 定义常量,为什么非要使用枚举?
- 枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
2.3、枚举的使用
enum Color//颜色 { RED = 1, GREEN = 2, BLUE = 4 }; enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。 clr = 5; //ok?? err错误,左右类型不一致
3、联合
3.1、联合类型的定义
- 联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。
#include<stdio.h> //联合类型的声明 union Un { char c; //1 int i; //4 }; int main() { //联合变量的定义 union Un u; //计算联合变量的大小 printf("%d\n", sizeof(u)); //4 return 0; }
- 联合体的访问和结构体类似都可以采用 . 操作符或 -> 指向操作符。
union un x; x.a = 10; union un* p = &x; p->a;
3.2、联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
#include<stdio.h> union Un { char c; //1 int i; //4 }; int main() { union Un u; //计算联合变量的大小 printf("%d\n", sizeof(u)); printf("%p\n", &u); printf("%p\n", &u.c); printf("%p\n", &u.i); return 0; }
- 利用联合体的空间分布可以巧妙判断出大小端:
#include<stdio.h> union un { int a; char b; }; int main() { union un x; x.a = 1; if (x.b == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
- 解析:
- 这里要把一组二进制序列 0x 00 00 00 01保存在a对应的空间里,此时四个字节每个都有地址,而地址具有高低之分,而我们的数据按字节对应的1bit位进行划分的时候,数据就有高低权值位之别,所以存储方案有两种,一种高权值位放在高地址处,低权值低地址处,如我们上图的左边存法,01放在低地址处,第二种方案相反,如上图右边将01放在高地址处,因为b永远在a的低地址处,b占一个字节,如上图x.b=1红色记号笔划分出,如果存储方案是第一种,那么b=1,如果是第二种,那么b=0,而第一种存储方案正式小端的存储法则,第二种正是大端的存储法则。
由编译器运行得知,是小端:
3.3、联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
- 例如:
#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)); //8 printf("%d\n", sizeof(union Un2)); //16 return 0; }
解析Un1的大小为8:
因为char类型自身大小为1字节,而编译器默认对齐数是8字节,对齐数取较小值为1字节,int类型自身大小4字节,对齐数取较小值为4字节。此时最大对齐数4字节,而char c[5]代表至少是5字节大小,又因为要是最大对齐数4的整数倍,所以增加到8字节。
解释Un2的大小16字节:
short c[7]代表该联合体大小至少14字节,short类型本身大小2字节,编译器默认对齐数8字节,此取较小值为2字节,int类型本身4字节,编译器8字节,此对齐数取较小值为4字节,最大对齐数为4字节,而14不是4的整数倍,所以增加到16字节。