二、位段
2.1、位段的定义
位段只能在结构体中使用,主要用来节省空间。
当有一些成员的取值范围有限的情况下,需要的内存空间可以在一定程度上减少。
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是整型,比如int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
struct A
{
int _a : 2; //a只需要2个bit位
int _b : 5; //b只需要5个bit位
int _c : 10; //c只需要10个bit位
int _d : 30; //c只需要30个bit位
};
//47个bit位
//struct A大小是6个字节?
int main()
{
printf("%d ", sizeof(struct A)); //8byte
return 0;
}
2.2、位段的内存分配
位段的内存分配原则:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4; //char类型最多一个字节,8bit,所以位段最多就是8
//也就是冒号后面的数字不能超过前面的类型的大小
//比如int类型,不能超过32
};
int main()
{
struct S s = { 0 };
printf("%d \n", sizeof(struct S));
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
结构体S的位段分配之后,结构体S的大小是?有两种情况:
(1)先开辟8bit,a使用3bit,剩5bit,然后b再使用4bit,剩1bit,此时不够c使用,再开辟8bit,然后c使用第一次开辟剩下的1bit和第二个开辟的4bit,此时第二次开辟的空间剩4bit,而d刚好是4bit,位段逐个使用,总共16bit,所以是2byte;
(2)先开辟8bit,a先使用3bit,剩5bit,然后b再使用4bit,剩1bit,此时不够c使用,再开辟8bit,c优先使用新开辟的8bit,使用后剩3bit,此时第二次开辟的空间剩3bit,不够d使用,所以再开辟8bit,d优先使用新开辟的4bit,剩4bit,总共24bit,所以是3byte。
通过在VS中分析,结构体S的大小是3byte,按照第二种方式分配空间。
分析:
结构体成员类型都是char类型,所以所以位段最多就是8bit,先开辟8bit空间供成员使用。
s.a=10,二进制为1010,位段只有3,所以存到空间中只有010;
s.b=12,二进制为1100,位段是4,所以存到空间中是1100;
s.c=3,二进制为0011,位段是5,所以存到空间中是00011;
第一次开辟的空间还剩1bit,不够用之后再开辟8bit,c优先使用新开辟的空间;
s.d=4,二进制为0100,位段为4,所以存到空间里是0100;
因为c使用第二次开辟的空间后,还剩3bit,不够d使用,所以再开辟8bit;
最终在内存中,可以看到S在内存中存储的3byte是62 03 04。
2.3、位段的跨平台问题
位段的使用也会带来一些问题。
1、int位段被当成有符号数还是无符号数是不确定的。
2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
三、枚举
3.1、枚举定义
可以用枚举类型(enumerated type)声明符号名称来表示整型常量。
使用enum关键字,可以创建一个新“类型”并指定它可具有的值。(实际上,enum常量是int类型,因此,只要能使用int类型的地方就可以使用枚举类型)
//枚举类型的声明
enum spectrum
{
red, //枚举常量后面是逗号 //0
orange, //1
yellow, //2
green, //3
blue, //4
violet //5
};
int main()
{
//枚举变量的创建并初始化
enum spectrum color = red; //给枚举变量赋值时,
//用枚举常量的可能取值进行赋值,不能使用其他值
printf("%d\n", sizeof(enum spectrum)); //4
printf("%d\n", sizeof(color)); //4
printf("%d\n", color); //0
return 0;
}
enum关键字;
spetrum作为标记名;
{ },花括号内的标识符枚举了spectrum变量可能有的值,称为枚举常量,枚举常量取值默认从0开始,依次向下递增,可以在定义的时候赋初值;
因此,color可能的值是red、orange、yellow等。
给枚举变量赋值时,用枚举常量的可能取值进行赋值,不能使用其他值。
enum DAY
{
Mon = 1, //改变枚举常量的值,后面一样也是依次往下递增
Tues, //2
Wed, //3
Thur, //4
Fri, //5
Sar, //6
Sun //7
};
3.2、枚举的优点
1、增加代码的可读性和可维护性;
2、和#define定义相比,枚举有类型检查,更加严谨;
3、防止了命名污染(封装);
4、便于调试;
5、使用方便,一次可以定义多个常量。
四、联合
4.1、联合类型的定义
联合可以在不同的时刻,保存不同类型和长度的对象的变量。其提供了一种方式,它能在同一个内存空间中储存不同的数据类型(不是同时储存),也就是成员共用一块空间,所以联合也叫共用体。
//联合类型的声明 union U { char c; int i; }; //联合变量的创建 union U u1; int main() { //联合变量创建 union U u2; //计算联合变量的大小 printf("%d\n", sizeof(union U)); printf("%d\n", sizeof(u2)); return 0; }
4.2、联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
//联合类型的声明
union Un
{
int i;
char c;
};
int main()
{
//联合变量创建
union Un un;
printf("%p\n", &(un.i)); //地址
printf("%p\n", &(un.c)); //地址
un.i = 0x11223344;
printf("%x\n", un.i); //以16进制打印
un.c = 0x55;
printf("%x\n", un.i);
printf("%x\n", un.c);
return 0;
}
与结构体的访问方式相同,联合访问方式也分为 点操作符 和 -> 。
即 联合名.成员 或者联合指针->成员 。
un.i和un.c的地址一样,说明它们两个共用同一块空间。
在对un.i 初始化后,其值为11223344;
然后对un.c赋值,因为两者共用相同的空间,调试的编译器是小端存储,所以un.c会在un.i的基础上重新赋值,55会将44覆盖。
4.3、联合大小的计算
(1)联合的大小至少是最大成员的大小。
(2)当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
联合也是存在内存对齐的。
//联合类型的声明 union U { char c; int i; }; //联合变量的创建 union U u1; int main() { //联合变量创建 union U u2; //计算联合变量的大小 printf("%d\n", sizeof(union U)); //4 printf("%d\n", sizeof(u2)); //4 return 0; }
联合成员c的大小是1byte,对齐数为1;联合成员i的大小为4byte,对齐数为4;
所以联合的最大对齐数是4,联合的大小是4的倍数。
联合成员c的大小是5byte,但是对齐数按照类型,所以对齐数是1;
联合成员i的大小是4byte,对齐数是4byte;
所以联合的对齐数为4,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍,所以联合的大小要对齐到4的整数倍,8byte。
//联合类型的声明 union U { char c[5]; int i; }; //联合变量的创建 union U u1; int main() { //联合变量创建 union U u2; //计算联合变量的大小 printf("%d\n", sizeof(union U)); //8 printf("%d\n", sizeof(u2)); //8 return 0; }