位段
1. 简单介绍
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s ={0};
s.a=10;//1010
s.b=12;//1100
s.c=3;//0011
s.d=4;//0100
return 0;
}
//char - 1字节 - 8比特位
//照下图的位段放置,结果是:
//0110 0010 0000 0011 0000 0100
// 6 2 0 3 0 4
- 位段最大位数目不能确定(机器位数不确定)(存在跨平台问题)
- 位段中成员在内存中无分配方向标准(右->左 / 左->右)
- 舍弃剩余的位与否不确定
- 最高位是不是符号位不确定
总结:和结构相比,位段可以达到相同的效果,但是可以很好的节省空间(不对齐),但是有跨平台的问题存在。
2. 例题
#define MAX_SIZE A+B
struct _Record_Struct
{
unsigned char Env_Alarm_ID : 4;
unsigned char Para1 : 2;
unsigned char state;
unsigned char avail : 1;
}*Env_Alarm_Record;
struct _Record_Struct *pointer = (struct _Record_Struct*)malloc
(sizeof(struct _Record_Struct) * MAX_SIZE);
//A = 2 B = 3 时 pointer分配多少字节空间?
结构体向最长的char对齐,前两个位段元素一共4+2位,不足8位,合起来占1字节,最后一个单独1字节,一共3字节。
另外,这道题的考点不止位段,还考了
#define
的查找替换,这题替换过后是3 * A + B
,即3 * 2 + 3 = 9
所以这题中pointer分配了9个字节的空间
int main()
{
unsigned char puc[4];
struct tagPIM
{
unsigned char ucPim1;
unsigned char ucData0 : 1;
unsigned char ucData1 : 2;
unsigned char ucData2 : 3;
}*pstPimData;
pstPimData = (struct tagPIM*)puc;
memset(puc,0,4);
pstPimData->ucPim1 = 2;
pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;
printf("%02x %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]);
return 0;
}
//这段代码的结果是?
puc是一个char数组,每次跳转一个字节,结构体不是,它只有第一个元素单独享用一字节,其他三个元素一起共用一字节,所以puc被结构体填充后,本身只有两个字节会被写入,后两个字节肯定是0。第一个字节是2就是2了,第二个字节比较麻烦,首先ucData0给了3其实是越界了,1位的数字只能是0或1,所以11截断后只有1,同理ucData1给的4也是越界的,100截断后是00,只有5的101是正常的。填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29
这段代码的结果是
02 29 00 00
枚举
1. 简单介绍
enum Colour
{
red,
blue,
green
};
int main()
{
printf("%d\n",red);//-> 0
printf("%d\n",blue);//-> 1
printf("%d\n",green);//-> 2
red=3;//报错
enum Colour colour = 5;//错误哦,类型不一致
return 0;
}
从这段代码我们可以发现
- 枚举使得代码可读性更强
- 枚举定义常量,且一次可定义多个
从一次可定义多个常量这点就可以看出枚举相较于#define
的优越性,实际上相较于#define
,枚举还有隐性的优点
- 不同于
#define
在预编译后就销毁数据,枚举不会,这使得其便于调试 - 枚举提供类型检查,更为可靠
2. 例题
enum ENUM_A
{
X1,
Y1,
Z1 = 255,
A1,
B1,
};
enum ENUM_A enumA = Y1;
enum ENUM_A enumB = B1;
printf("%d %d\n", enumA, enumB);
//这段代码运行的结果是?
枚举默认从0开始,所以X1是0,故Y1是1,给了数字后会根据数字向后推,那么Z1是255,A1是256,所以B1是257
如果不知道给定数字后向后推,那就很迷茫了。当然,知道这个知识点之后这题是很无脑的。
联合(联合体)
1. 简单介绍
union UN
{
char c;// 1
int i;// 4
double d;// 8
};
int main()
{
union UN un;
printf("%d\n",sizeof(union UN));
printf("%d\n",sizeof(UN));
printf("%p\n",&(un));
printf("%p\n",&(un.c));
printf("%p\n",&(un.i));
printf("%p\n",&(un.d));
return 0;
}
运行后我们不难发现,以下四行代码打印出的地址一致,我们可以得到联合体的一个特征
![image-20220922103023726](https://i-blog.csdnimg.cn/blog_migrate/dca5bce6e945be9b93adecfb3a88f120.png)
联合体内的元素共用一个地址,但不能同时使用
![IMG_0601](https://i-blog.csdnimg.cn/blog_migrate/1b8433b8f2a77567fa7181cfc7e97f05.png)
关于数据的存储,一直都存在一个问题:顺序问题。只要数据的内存空间超过一个字节,就会涉及顺序的问题。
有一个经典的问题:大小端存储的判断,在这里可以用联合体来巧妙得实现判断:
int check_sys()
{
union UN
{
char c;
int i;
}u;
u.i=1;
return u.c;
}
int main()
{
int ret=check_sys();
if(ret==1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
![IMG_0600](https://i-blog.csdnimg.cn/blog_migrate/ade5856d9b545ec6ed7a2c4fcf80fba4.png)
结合图像,我们能更深刻地理解联合体 共用一个地址 的含义。
2. 例题
//X86架构下,小端字节序存储,有下列代码
#include<stdio.h>
int main()
{
union
{
short k;
char i[2];
}*s, a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf(“%x\n”,a.k);
return 0;
}
//运行结果是?
这题中union只有2字节,2字节的十六进制只有4位 。而位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,所以是
3839
n
{
short k;
char i[2];
}*s, a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf(“%x\n”,a.k);
return 0;
}
//运行结果是?
> 这题中union只有2字节,2字节的十六进制只有4位 。而位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,所以是`3839`