位段
位段的声明与结构体类似,有两个不同:
- 位段的成员必须是
int
、signed int
、unsigned int
或char
(属于整形家族) - 位段的每个成员后边有一个冒号和一个数字(这个数字大小不能超过前面数据类型的大小)
下面是一个位段:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
位段,位指的是二进制位。
位段的存在是为了:有的变量不需要太大的空间,有的时候两三个比特位就够了,这是就可以使用位段。
位段的内存分配
- 位段的成员必须是
int
、signed int
、unsigned int
或char
(属于整形家族) - 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
下面来看一下位段A的内存分配:
位段的成员是int
,以4个字节开辟,先开辟4个字节也就是32比特位
- 1.先分配
_a
,_a
占2比特位,此时4字节还剩30比特位,足够分配下一个的_b
- 2.分配
_b
,_b
占5比特位,此时4字节还剩25比特位,足够分配下一个的_c
*3. 分配_c
,_c
占10比特位,此时4字节还剩15比特位,不够分配下一个的_d
,跳过当前的剩下比特位,再会开辟4个字节,将_d
存到下4个字节中 - 4.分配
_d
,_d
占30个比特位,_d
存到第二个4字节中
所以A
占8字节空间大小
int main()
{
printf("%d", sizeof(struct A));
return 0;
}
其实这8个字节是根据
A
中成员的情况,是同时开辟的,前面分析中的“先开辟”,“再开辟”等语句是用来分更好地去讲解位段内存分配的,并不是先后开辟的空间
位段变量内存的使用
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
int main()
{
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
}
由前面可知,S
占3个字节,内存中开辟24个比特位
对于一串空间,是从左到右使用空间还是从右到左使用空间,C语言标准是没有定义的,接下来,我们假设从低位(高地址)到高位(低地址)使用(从右到左)
- 先分配
s.a = 10;
10的二进制是1010,a
只有3个比特位空间,所以截断,将010放到内存中,此时第一个1字节空间还有5比特位,够下面的b
使用 s.b = 12
,12的二进制是1100,b
有4个比特位空间,将1100放到那5比特位中,第一个1字节空间·还有1比特位,放不下后面的c
,所以这个剩下的1比特位浪费掉s.c = 3
,3的二进制是011,c
有5个比特位空间,所以将00011放到第二个1字节空间里,还剩3比特位空间,不够后面的d
,这3个比特位浪费掉s.d = 4
,4的二进制是0100,d
占4个比特空间,所以将0100放到第3个1字节空间中
位段不存在内存对齐,因为位段的存在就是为了节省空间,如果还有内存对齐,反而还会浪费空间
位段的跨平台问题
- nt 位段被当成有符号数还是无符号数是不确定的。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
所以有跨平台需求时,不要使用位段。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
枚举
枚举就是把可能的取值一一列举
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
上面定义的enum Sex
就是枚举类型,大括号里的内容都是枚举类型可能的取值,叫做枚举常量
这些枚举常量类型都是int
如果在定义枚举时,没有对里面的枚举常量赋值,则默认从0开始,一次递增1
enum Sex
{
MALE,
FEMALE,
SERCET
};
int main()
{
printf("MALE:%d\n", MALE);
printf("FEMALE:%d\n", FEMALE);
printf("SERCET:%d\n", SERCET);
}
也可以在定义的时候也可以赋初值
enum Sex
{
MALE = 4,
FEMALE = 6,
SERCET = 9
};
int main()
{
printf("MALE:%d\n", MALE);
printf("FEMALE:%d\n", FEMALE);
printf("SERCET:%d\n", SERCET);
return 0;
}
如果只对其中某个枚举常量赋初值,则后面的枚举常量的值也一次递增
enum Sex
{
MALE = 4,
FEMALE ,
SERCET
};
int main()
{
printf("MALE:%d\n", MALE);
printf("FEMALE:%d\n", FEMALE);
printf("SERCET:%d\n", SERCET);
return 0;
}
对枚举变量赋值的时候尽可能用枚举常量赋值,如果用其他整形赋值的话会涉及强制转换,在C中会编译成功,而在C++中不会成功
int main()
{
enum Sex s = MALE;//尽可能用可能取值去赋值
return 0;
}
枚举的优点
我们可以使用#define
定义常量,但是与枚举相比,还是枚举更好一点
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
联合
联合又称为共用体,是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间
union Un
{
char c;
int i;
};
上面的union Un
就是一个联合
typedef union Un
{
char c;
int i;
}un;
int main()
{
un u;
printf("%p\n", &u);
printf("%p\n", &u.c);
printf("%p\n", &u.i);
return 0;
}
输出结果是:
可以看见,u
,u.c
,u.i
的地址都相同,就说明联合的成员是共用同一块内存空间的
联合un的储存形式如图:
一道面试题:
判断机器的大小端存储
我们可以通过联合,定义联合,里面有一个
char
类型,一个int
类型,定义一个联合变量un,给un.i赋值1
因为联合共用一块内存空间,如果机器是小端存储,就会在首字节中存01,如果大端存储,就会在首字节中存储00,之后可以通过un,c
取出首字节中的内容
进而可以判断是大端还是小端
int main()
{
union
{
char c;
int i;
}un;
un.i = 1;
if (un.c == 1)
{
printf("小端存储");
}
else
{
printf("大端存储");
}
}
联合大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
计算Un1的大小:
union Un1
{
char c[5];
int i;
};
int main()
{
printf("%d", sizeof(union Un1));
}
Un1
中有2个成员,一个是char c[5]
,一个是int i
字符数组中每个成员都是char
,所以char c[5]
的对齐数是1,int i
的对齐数是4。
此时最大成员大小是5,而对齐数是4,5不是4的整数倍,所以会浪费3个空间,是联合的大小扩大为8,是对齐数4的整数倍
输出结果:
计算Un2的大小:
union Un2
{
short c[7];
int i;
};
printf("Un2:%d\n", sizeof(union Un2));
short c[7]
的对齐数是2,大小是14
int i
的对齐数是4,大小是4
此时,最大成员大小是14,最大对齐数是4,14不是4的整数倍,所以联合大小应扩大为16