作者:~小明学编程
文章专栏:C语言基础知识
格言:目之所及皆为回忆,心之所想皆为过往
目录
位段
什么是位段
首先就是位段的声明,位段的声明和结构体类似但是也是有区别的。
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
例如我们下面的这段代码
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
这里我们就声明了一个位段A。
位段的内存分配
位段的大小
在位段中我们冒号:后面的数字代表的是比特位也就是说我们的_a成员占两个比特位,我们的_b就会占5个比特位,直到我们的_d分配的时候会发现我们int类型的4个字节也就是32个比特位不够用了,这时候我们就会向系统再次申请一个int类型的空间,至此我们一共申请了两个int类型的空间,也就是8个字节。所以我们这个位段的大小就是8。
内存分布
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
上面就是位段的内存分布了。
位段数据的输出
现在我们已经将数据给存储进去了那么我们怎么取出数据取多少怎么打印呢?
struct A
{
int _a : 2;
int _b : 5;
int _x;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));
struct A a = { 0 };
a._a = 10;
a._b = 2;
printf("%d", a._a);
return 0;
}
我们还拿最开始的例子来讲解,现在我们给_a赋上了一个10,那么将会打印出什么结果呢?
这时我们的位段大小很容易得出是16但是我们存进去的10居然打印出了一个负数,这时因为我们的_a只能存两个字节进去,1010也就是存进去了10当我们打印的时候将会发生一个整型提升我们的10将会变成
11111111111111111111111111111110
然后变成源码就是
10000000000000000000000000000010
所以就打印除了-2。
位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
枚举
枚举顾名思义就是一一的列举,例如我们一周有七天我们就将七天一一的列举下来。
枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
联合(共用体)
联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
例如:
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
联合体的特点
联合体最大的特点就是联合,联合它们的内存空间。
union Un
{
char c;
int i;
};
int main()
{
union Un un;
printf("%x\n", &un.c);
printf("%x\n", &un.i);
return 0;
}
可以看到我们的初始的地址都是一样的,
在我们了解完联合体的特点之后我们就可以去尝试着利用这一特点去解决我们的大小端的问题。
之前我们已经用强制类型转换的的方法去判断的我们机器的大小端,现在我们用联合体的方式去判断一下。
union Un
{
char c;
int i;
};
int main()
{
union Un un;
un.i = 0X11223344;
if (un.c == 0X11)
{
printf("大端\n");
}
else
{
printf("小端\n");
}
return 0;
}
可以看出我们的机器是属于小端的,我们的un.i是int类型的数据,占4个字节,而我们的un.i只占一个字节,然后我们的un.c必然是从低地址拿数据结果拿到了一个高位的数据0X44所以输出小端。
联合体大小
我们该怎么计算一个联合体的大小呢?
我们知道联合体的成员公用同一块的空间,所以我们至少要保证能容得下最大的那个成员,下面给个例子。
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
难道我们输出的是5和14吗?当然不是我们计算联合体还有一条规则那就是:
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
我们的Un1中char c[5]占5个字节,而我们的最大对齐数则是4个字节5不是4的倍数所以提升到8,
Un2同样如此short c[7]一共占14个字节最大对齐数为4个字节,所以提升到16。