结构体
1、结构体
结构体的类型是否一致:取决于是否是同一个结构体定义的变量,即使两个结构体完全相同
结构体允许初始化但不允许赋值
结构体:能够提升变量的访问效率
2、结构体的自引用 (typedef 重命名)
typedef struct node{
int data;
struct node *next; //结构体自引用
}Node;
3、结构体的大小 ----- 内存对齐
结构体内部是存在内存对齐情况的,内存对齐问题会影响结构体大小
结构体传参不会发生降维问题(硬拷贝),但是采用指针效率高
4、为什么要有内存对齐?
(1)有些硬件平台并不支持内存的随意地址访问,只能从指定位置开始访问指定大小的数据;
(2)若果让成员变量对齐到指定的位置,更加便于 CPU 对数据的访问。
内存对齐规则:
(1)第一个成员在结构体变量偏移量为 0 的地址处;
(2)其他成员变量要对齐到某一个数字(对齐数)的整数倍的地址处;
对齐数==编译器默认的一个对齐数与该成员大小的较小值
(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍;
(4)如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍;
(5)每个平台有一个固定默认的对齐数,VS 默认对齐数为 8 。
下边列举一些例子来直观的分析内存对齐:
首先需要了解:
double ------ 8
short ------- 2
char -------- 1
int ----------- 4
eg1:
struct AP{
double b; // 8
char a[3]; // 1*3
short **c;
//8+3=11,不能整除指针大小 4,需要进行内存补齐到 --- 12
//指针类型大小为4,需补一个空间:1+4
}
//总大小:8+3+1+4=16 能够整除最大对齐数8----so,最终结果为 16
eg2:
struct A{
short a; //2
char b; //1
double *c[3];
//2+1=3 ,不能整除double -- 4,需要补齐 1 字节,1+4*3
//总大小:2+1+1+4*3=16 能够整除最大对齐数 4 ----- so,最终 16
}
eg3:
struct A{
double a; //8
char b; //8 能整除char -- 1,所以不需要补齐,1
int c;
//8+1=9 ,不能整除int -- 4,需要补齐到--12,3+4
//总大小:8+1+3+4=16 ,最大对齐数--8 ,能够整除
};
struct B{
char d; //1
struct A e;
//16,1不能整除struct A 的最大对齐数 8
//需要补齐到--8字节,7+16
double f; //1+7+16=24 能够整除double -- 8, 8
//总大小:1+7+16+8=32 ,能够整出最大对齐数 8
}
#pragma pack() 设置对齐数----1,2,4,8,16
位段
内存空间的整体排布是受平台影响的。
位段:让结构体中的多个成员变量可以使用同一块空间中的不同比特位进行数据存储,以此节省使用空间
1、位段的空间分配:采用“压缩存储”的方式,如果剩下的比特位数目能够容纳当前变量,大家就挤一挤,如果不能,重新开辟,单位是位段内部的一致类型大小
2、name:n 表示使用指定空间中 n 个比特位存储数据,并且多个位端可以合并占用同一个存储空间
3、位端:
可以节省空间;
是从低位开始逐位存储;
若位端存储上的数据过大则会产生截断;
第一个位端剩余空间不足以存储下一个位端时,会重新开辟新空间来存储;
位端的数据类型必须使用整形 : int 、char 、short ,不能使用浮点型 float
struct A{
int a:2; //表示 a 变量占据 2 个字节空间
int b:5; //表示 b 变量占据 5 个字节空间
int c:10;//表示 c 变量占据 10 个字节空间
int d:30;//表示 d 变量占据 30 个字节空间
}
int main()
{
printf("%d\n",sizeof(struct A));
}
由于 a 变量占据 2 字节,b 变量占据 5 字节,c 变量占据 10 字节,d 变量占据 30 个字节空间。由于首先开辟的 32 个字节(int)空间不足够存储 d 变量,因此需要重新开辟新空间来存储 d 空间。
多个位段使用同一个空间,若空间足够存储,则公共使用同一个空间,若不够存储,则多出来的位端需要重新开辟新的空间来存储。
4、数据截断:位段确定了一个变量存储的数据所占字节数,若给定数据超出所占字节数则会发生截断
struct tmp_t {
char a : 3;
char b : 6;
};
如上,a 变量占据 3 个字节空间,但假若给 a 赋值 a=9 ; 9==1001应该占据 4 字节大小,超出了给定空间,因此会发生截断,存储的实际内容为 001,也就是 1。
枚举-----enum
将所有元素一 一列举,各枚举变量之间用逗号隔开;
枚举类型本质是 int,而且默认是从 0 开始依次递增的;
枚举 VS 宏定义:
枚举是一种类型,在编译时候会进行类型检查;
宏是基于替换原则,在这个过程中并不进行任何检查工作
enum ENUM_A
{
X1, //默认 0
Y1, // 1
Z1 = 255, // 255
A1, // 256
B1, // 257
};
enum ENUM_A enumA = Y1;
enum ENUM_A enumB = B1;
printf("%d %d\n", enumA, enumB); //1 257
联合----union
(大小端)在使用union 时候,一次只会访问一个元素
1、联合体本身的大小并不是把所有类型加起来的大小,常规而言,联合体大小是由联合体内最大元素的大小决定的,决定之后所有元素共享空间
2、联合体也要考虑内存对齐问题,联合体的最终大小要能整除联合体内最大对齐数
思考:
下例联合体大小为?
union tmp {
char name[5];
int age;
};
是 5 ?还是 4?
其实都不是!!实际大小应该为 8
union tmp {
//char name[5];
//int age; //sizeof(union tmp) = 8
//double a;
//char name[5]; //sizeof(union tmp) = 8
double a;
char name[10]; //sizeof(union tmp) = 16
};
3、联合体的适用场景:
(1)大小端的检测场景;
(2)在不同场景下适用不同数据
//方法一:
int a = 1;
char* b = &a; //判断 b 指向值
//大端:低地址存高位,低地址为 0 则为大端
//小端:低地址存低位,低地址为 1 则为小端
//方法二:
union tmp_t {
int a;
char b;
};
int main()
{
tmp_t tmo;
tmo.a = 1;
if (tmo.b == 1) { //利用联合体空间共享
//小端
}
}
小练:
eg1:
#include<stdio.h>
int main()
{
union
{
short k; // 2
char i[2]; // 2
}*s, a;
s = &a;
s->i[0] = 0x39; // 十六进制
s->i[1] = 0x38;
printf(“%x\n”,a.k); // 3839
return 0;
}
short 读取两个字节空间,char 类型数据存储低地址在低处,因此 39 在地位,38 在高位,所以读取 a.k 两个字节为 3839
eg2:
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]);
//输出结果为 02 29 00 00
return 0;
}
(说明:博客内容为原创,包含个人理解思路,如有疑问请留言哦~)