前言
嗨,我是firdawn,在本章中我们将介绍,结构体变量的创建和初始化,结构成员访问操作符以及结构体的内存对齐,下面是本章的思维导图,接下来,让我们开始今天的学习吧!
一,结构体类型的声明
1.1 结构体的一般声明
这里声明的结构体是有名字(Stu)的。
1.2 结构体的特殊声明
在结构体的声明中,有一类特殊的声明为匿名声明,如下图
这里就可以懒得起名字了,对不对,不过匿名声明有很多缺点,例如:
1.匿名结构体只能使用一次,只能在声明这个结构体的同时创建结构体变量。
2.如下图
在这里编译器会将两个结构体当成不同的类型,所以这样赋值是非法的。
总结:那么,既然匿名声明有诸多麻烦,编者建议大家尽量少使用匿名声明。
1.3 结构体的自引用
我们在定义结构体时,如果要在结构体成员中包含⼀个类型为该结构本⾝的成员,应该怎么做呢?如图(错误示范),
在结构体 U 中,成员struct U展开又会包含自己,这样无限循环下去,这个结构体就会无限大。
所以,这样自引用是不行的,那我们应该怎么做呢,才能在结构体中又包含⼀个类型为该结构本⾝的成员,正确答案如下图,其实我们使用指针就可以了。
二,结构体变量的创建和初始化
2.1 结构体变量的创建
结构体变量的创建有两种方式:
1.在声明结构体的同时创建结构体变量
2.在使用时,用结构体类型创建结构体变量
2.2 结构体变量的初始化
三,结构成员访问操作符
3.1 结构体成员访问操作符有两种,用于访问结构体成员:. 和 ->
-
对于结构成员访问操作符 “ . ”,它的操作对象是结构体成员
-
对于结构成员访问操作符 “ -> ”,它的操作对象是结构体指针
四,结构体传参
4.1 传结构体变量
在这里,调用函数时,传的是变量,为值传递,形参是实参的一份临时拷贝,改变形参不影响实参。
4.2 传结构体地址
在这里,调用函数时,传的是地址,为址传递,我们可以通过解引用操作来改变结构体变量stu1。
五,结构体内存对齐
5.1 对齐规则
- 结构体的第一个成员的起始地址处,默认为偏移量为0的位置,每过一个字节,偏移量加一。
- 结构体的每一个成员都需要对齐到成员对齐数的整数倍的偏移量位置处。
对齐数=成员所占的字节大小与编译器默认的最大对齐数之间的较小值。 - 结构体的总大小必须是其内部成员中的最大对齐数的整数倍。
不放数据的空间我用了灰色填充。
5.2 为什么存在内存对齐?
- 平台移植原因,某些平台,只能在特定的地址处,取出特定的数据。
- 性能原因,如果编译器处理数据时,一次读取8个字节,那么我们储存一个8字节的数据,如果采用了内存对齐规则,编译器只读取一次就可以拿到数据,否则我可能需要读取两次才能拿到数据。。总的来说,这是一种拿空间换时间的做法。
- 我们既然已经知道的内存对齐,那么,如果我要尽量减小空间消耗,应该怎么做呢?其实,我们这时候可以尽量将相同数据类型的成员放到一起,这样,就可以节省空间啦。
5.3 修改默认对齐数
我们可以通过使用预处理指令#pragma pack(4),修改最大对齐数为4。
六,结构体实现位段
6.1 什么是位段
位段的声明和结构体是类似的,不过有两个地方不同。
- 位段的成员必须是int unsigned int 或者 signed int,不过在C99标准中,位段成员可以上其他的类型。
- 位段的成员名后面有一个冒号和一个数字。
6.2 位段的内存分配
位段存储时,编译器分配空间是一个字节一个字节给的,使用空间的时候,一个位段成员所占空间的大小(单位是bit)取决于你定义它时指定给它的空间大小。不过空间是从左向右使用还是从右向左使用,标准是未定义的,取决于具体的编译器,在VS2022中,位段成员的空间使用是从右向左的。
6.3 位段的跨平台问题
- int 位段被当成有符号数还是⽆符号数是不确定的。
- 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会
出问题。 - 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
- 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利⽤,这是不确定的。不过在VS2022中是舍弃这块空间。
6.4 位段的应用
由于位段能节省空间的优点,位段可以用于IP数据报的传递,这样就可以减小网络的延迟。
6.5 位段使用的注意事项
位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是输⼊放在⼀个变量中,然后赋值给位段的成员。