结构体类型
如图,结构体的关键字是struct,stu是结构体的标签。没有标签的结构体就是匿名结构体,匿名结构体只能使用一次。
下面的name、age、height是结构体的成员。
struct stu 与int /float一样,是类型,可以用来创建变量。不一样的是,stu是自己定义的标签。而结构体声明以后,跟着的s1,s2是结构体的变量。
struct stu
{
char name[];
int age;
float height;
}s1,s2;
int main()
{
//...
struct stu s1 = {"zhangsan",13,170.1};
return 0;
}
除了上图显示的,还有另一种初始化方式。
struct stu s2 ={.age = 17, .name = "lisi", .height = 190.2};
访问结构体变量的具体内容。结构体变量名 . 结构体成员名。
printf("%d\n",s1.age);
printf("%f\n",s2.height);
printf("%s\n",s1.name);
也可以通过指针访问。结构体指针 -> 结构体成员名。
struct stu *p = &s1;
printf("%d\n", p->name);
结构体内存对齐
创建结构体也需要向内存申请空间。而不同的成员位置也会导致申请的空间大小有所变化,这是因为结构体内存需要对齐导致。
如下图所示,成员相同的结构体,申请的空间会不一样。
struct stu1
{
int age;
char name;
char email;
};
struct stu2
{
char name;
int age;
char email;
};
结构体对齐有4条规则:
1.结构体的第一个成员对齐到结构体变量偏移量为0的地址。
2.结构体的其他变量要对齐到对齐数的整数倍。
对齐数:变量自身的大小(如int是4个字节)与编译器默认对齐数(如vs是8)中,较小的那个数。(4<8,所以int创建的变量在vs平台下的对齐数就是4)
3.结构体总大小为最大对齐数的整数倍。(比如上面创建的结构体stu2,最大对齐数是4,所以结构体总大小就得是4的整数倍)
4.结构体有嵌套的情况下,嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍上,结构体总大小是内部所有成员中最大对齐数的整数倍。
虽然申请了8个字节,但其实两个字节没有被占用浪费掉了。后面的stu2也是这样。浪费了6个字节。
为什么要有对齐数?
1.有些编译器只能从特定的对齐数上取数,比如int型从4的对齐数取数,如果不采用对齐,那么取不到数。
2.编译器在读取内存的时候有些是8个字节8个字节的取,如果不采用对齐数,就可能出现一次读取没读到完整的字节,要读取好几次才能读取到一个变量。有了对齐数,一次就能读取到,运行速度提高很多。
采用对齐数,就是用空间的些许浪费换取时间。
结构体实现位段
位段声明
struct A
{
int a : 2;
int b : 4;
int c : 30;
};
位段和结构体不同的有两点:
1. 位段的成员只能是整型家族的,包括int 、 unsigned int 、 signed int 、char
2. 位段后面有个冒号还有个数,这个数的单位是比特,代表这个变量可以占用的内存大小。
那么,整个结构体A所占用的内存是多少呢?
2+4+30=36bit
但实际上不只。
位段的内存申请不是按照比特申请的,是按照char和int的方式,也就是1个字节或者4个字节来申请的。
每个编译器分配内存的方式不一样。这是int型,先给32个bit,放不下了再申请4个字节。但是从左往右放还是从右往左放(从高地址往低地址放,还是从低地址往高地址放)都不确定的。以及,剩下的内存放不下了是先填满再放,还是直接把剩下的浪费掉,把数据存放在新申请的内存放。不同编译器都不太一样。这个要自己测试。
所以这个struct A在VS上占用8个字节。
位段使用虽然比较节省空间。但是不太方便跨平台。所以一般是在测试了解了编译器存放位段方式之后再使用的。
位段应用场景
位段可以用于网络协议ip数据报的形式,有些只需要传8个比特或4个比特,那么就可以用到位段,而不至于每种内型都申请4个字节(那样传输效率慢也占用空间)。
除此之外,还有许多其他运用场景。