一、struct 特点和注意事项
1.结构体定义
struct stu1
{
int num;
char name[20];
double score;
}s1;
struct stu2
{
int num;
char name[20];
double score;
}s2;
PS:这里定义了一个结构体 stu1,并实例化了一个对象s1。但是注意,这里虽然stu1和stu2里面定义的变量是一样的,但是它们还是两个不同的结构体。
结构体最后有一个分号,要注意不要忘记写
2. 匿名结构体
struct
{
int num;
char name[20];
double score;
}s2;
定义结构体的时候,可以不用定义结构体类型名,此种写法叫做匿名结构体,用此结构体实例化的s2,使用只能使用一次
3.typedef类型重命名
一般自定义的数据类型,都可以使用typedef来重命名。
typedef struct Node //类型重命名
{
int i;
char c;
}Node,*pNode;
这里把struct Node重命名为了Node,把struct Node* (指针类型)重命名为了*pNode,以后进行结构体变量的声明的时候
就可以简化写法,struct Node N,这种定义的结构体变量的写法简化为,Node N。指针声明,struct Node* pN,简化为,pNode pN
PS:在c++的语法里面,不用typedef也可以简化写法。 匿名对象不能重命名
4.结构体嵌套和变量初始化
struct Book
{
int num;
char name[20];
char id[12];
};
struct Stu
{
struct Book HG;
struct Stu* next;
}HG={{1001,"zhaoshuang","ZS001"},NULL};
会发现这里把Book嵌套进了Stu里面。这种写法如果不是很明白可以这样理解,一个学生Stu在图书馆里借了一本书,因为是图书馆的书,书也有名字和编号。
二、结构体大小的计算方法
1.一般结构
struct S
{
char c1;
int i;
char c2;
}s1
sizeof(s1)
这里用sizeof(s1),计算出结构体大小为12字节,发现如果按照直接1+4+1=6,来算比这个多出6个字节。
这是因为结构体在内存中的存储,是按照一定规则来排列的。
首先c1要放在在一个偏移为0处的地址,放好后到下一个偏移1,然后判断此地址的偏移是否是要存放的数据对齐数的整数倍。
这里1不是i (4字节)的整数倍,会被自动补齐到偏移4处,而4是4的整数倍, 然后从偏移4处往后放4个字节,到达偏移8,8是1的整数倍,放置变量c2,到达偏移9处。
最后结构体的大小必须是最大对齐数的整数倍,这里最大对齐数是4,而已经存了9个字节,但是9不是4的整数倍所以这里补齐到11偏移处,整体大小为12字节,12是4的整数倍,所以这里12就是结构体大。
PS:这里蓝色的区域就是补齐后浪费的区域。
计算结构体大小时候,不一定是按照结构体中的变量自身的大小来做对齐数,对齐数是变量自身对齐数和默认对齐数的较小值
如果默认对齐数是4,那么一个double类型的数据也要按照4字节来对齐。
PS:vs中默认对齐数的大小是8,Linux系统中没有默认对齐数,对齐数就是变量本身的大小
2.注意事项
offsetof -- 一个宏,计算变量相较于起始地址的偏移量
struct s1
{
char c1;
int i;
char c2;
};
printf("%d\n",offsetof(struct s1,c1));
printf("%d\n",offsetof(struct s1,i));
printf("%d\n",offsetof(struct s1,c2));
可以看到跟我们实际计算是一样的。
#pramga pack(4);
使用这行代码就可以改变编译器的默认对齐数。括号里面是几对齐数就是几。这里要注意设置默认对齐数的时候,是为了让某些数据跟好的对齐,所以不要把对齐数改为1和其他奇数,或者0,变为1就是没有对齐数,顺序排列,0是错误的写法。
3.结构体嵌套的大小计算
struct s3
{
double d;
char c;
int i;
}; //结构体大小为 16
struct s4
{
char c1;
struct s3 s3;
double d;
};
先把c1存在偏移为0的地址处,到偏移1。
然后存放结构体s3的时候, s3要对齐到自己的最大对齐数的整数倍,自己最大对齐数是double类型的变量d,对齐数为8,1不是8的倍数,补齐到偏移8处,再加上s3的大小16,到了偏移24处。
24是8的倍数,所以加上8到了31偏移处,结构体整体大小为32,这里最大对齐数包括嵌套的结构体的最大对齐数,这里s3跟s4都最大的对齐
890数都是8,32是8的整数倍,所以结构体s4大小为32字节
4.巧算
PS:这里偏移处要存一个字节,所以这里才到偏移31处,而不是32处,但是整体存的字节是32个字节
那么你自己计算的时候就不用一定要按照计算机的规定来,这里我们自己计算的时候就可以把结构体想成是从偏移1处直接放置的。
struct s3
{
char c;
double d;
int i;
};
那么直接加到几就是几,这里c在偏移1处就是1,1补齐到8,8再加8就是16,16再加4就是20,20不是8的倍数补齐到24。那么24就是结构体的大小。
当然如果还不是特别理解结构体的话,还是老老实实计算吧。
三、结构体为什么需要对齐
那么为什么结构体要浪费空间进行数据对齐呢,直接一个挨着一个排不是节约空间吗?
其实在x32位系统中,计算机访问内存一次4个字节。假设一段内存存了a(4字节)、b(3字节)、c(4字节)三个变量,一个顺序排列,一个对齐排列。
根据图示我们会发现,这里要访问c变量的话,要进行两次访问。要是访问b变量的话,则会访问到一部分c的数据。看似节省了空间但是降低了效率
而这里对齐访问的话,访问c值需要一次就好,访问b不会访问到多余的数据。看似浪费了空间,但是却提升了效率
所以结构体的数据对齐是空间换时间的方法。
拓展:
位段:
位段是用来节约空间的一直写法,不考虑对齐。
struct A
{
int a:2; //:冒号后面的意思是此变量,能用的字节数
int b:5;
int c:10;
};//位段没有对齐,其目的就是为了节省空间
EXMPLE:
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;
这里结构体首先申请一个字节存储a,a使用3个比特位,b使用4个比特位。到c的时候,存不下了,再申请一个字节,c使用5个比特位,剩下的不够存d,那么再申请一个空间来存储d,d使用4个比特位。
然后把数据存储进去,a=10,10 = 二进制 1010 ,但是只能a存储三个比特位,把前三个比特位101存了进去。b=12,12 = 二进制 1100,b有4个比特位可以全部存进去。c=3,3 = 二进制 011,也可以全部存入,d=4,4 = 二进制 0100,也可以存入,所以这里3个字节的在内存中的存储为。
在内存中就是 0x62 03 04
联合体:
关键字union,也是一种自定义的数据类型,特点是联合体变量成员共用一块空间。联合体大小,最小都是最大变量的大小。
要注意和结构体不一样,结构体是每一个变量都有一个独立的空间
union Un
{
char c;
int i;
}
Un u;
sizeof(u);//u大小为4,跟struct不同
这里计算的大小是4字节。但是注意联合体也是存再数据对齐的。
union Un
{
char arr[5]; //5字节
int i; //4字节
};
这里arr数组大小是5个字节,但是对齐数还是char类型的大小1,i 的对齐数是4,因为共用一块空间且大小为最大变量的大小,那么联合体大小就是5。
但是因为存在对齐的情况,5不是4的最大对齐数4的整数倍,所以补齐到8,那么这里8就是联合体的大小。
利用联合体特性判断机器大小端:
int checl_sys()
{
union D
{
char c;
int i;
}u;
u.i=1;
return u.c
}
//如果 返回的是0,则是大端
//如果 返回的是1,则是小端
因为这里i和c共用空间,如果是小端存储 01 00 00 00,读取char就会读取到相同空间中i的首地址存储的数据,那么如果是1就证明机器是小端存储数据。