C :结构体详解
C语言中有好几种自定义类型,他们都有很大的作用。
今天讲解结构体——内部有好多成员组成的一个集合
一、结构体的声明
1.普通的声明
首先要写上结构体的关键字 struct ,之后跟上类名,在大括号里面写上结构体内部的成员,
大括号后面可以直接定义几个变量,用逗号相隔,也可以什么都不定义。
struct name {
number-list;
} virable-name;

结尾的分号不能丢
如图所示,do_it结构体有六个成员,结构体外同时创立了 c1 c2 c3 三个变量。
2.声明的简化
声明完成后,只要用 struct name 变量名 ;来定义变量

还可以利用 typedef 简化上述的结构体变量的定义

3.结构体的自引用
当你在一个结构体声明时想要它的成员包含它本身时,结构体的大小就无限大了,是违规操作
有一种挽救方法为把内部的成员换为指向该结构体的指针。

4.匿名结构体
当结构体没有名字时,也就是定义了个匿名结构体:
只有声明时的建立的变量能有结构体的相应内容,只有它有匿名结构体的内容
也就是说 xdo_itx 成为了 绝唱,用完之后再也不能有其他相同结构的结构体,除非你又定义了一个一样的;尽管你之后建立一个相同的结构体,并生成了一个指向它的指针,
那这个指针不能拿来取 xdo_itx 的地址(下图二所示)

typedef的妙用
匿名结构体可以跟上 typedef ,进行新的声明,如下图

注意点: 当你自引用你的结构体,主要是用它的指针时,匿名结构体内部是无法用你新声明的类名来直接进行指针类型的,因为它先内部成员定义完后才有外部的 typedef 的重构,也就是逻辑的先后顺序不正确。

二、结构体的初始化
定义好一个结构体时,初始化有两者方法

我将一一讲解两种方法
按序排放
上图中 a1 的初始化就是按顺序进行的,按你定义结构体时的顺序
乱序排放
利用点操作符对应到成员名,做到按你想要的顺序初始化
三、结构体成员的访问操作符
1.直接访问
一般想知道结构体成员的值时,用的是 '.' ,点操作符,用法为
结构体类名.成员名,把前面的整体当一个变量使用

2.间接访问
知道结构体的指针之后,可以用 结构体指针-> 成员名 来访问结构体
效果和点操作符一样

四、结构体的大小计算
结构体的大小不是直接内部成员的大小相加而得,有一种内存对齐方式在结构体大小决定中有着关键作用。比如一个包含了 int a1 ; char a2 ;的结构体,字节大小不是5 ,而是8

注!!以下的内容十分连贯,必须挨着看过去才能懂内存对齐
偏移量
结构体在占用内存时,首先选取一个地址作为起始位置,同时偏移量为0 ;
之后每向后移动一个字节,偏移量会增加一。结构体的第一个成员都是放在偏移量为0 的地址处。
对齐数
在放置每个成员时,还有一个操作在获得对齐数:
比较编译器的默认对齐数和该成员的字节大小,取两者的较小值(vs默认对齐数为 8)
之后摆放新成员时,必须放在偏移量为对齐数的整数倍的位置
(第一个成员直接放0 ,但它也会比较获得对齐数)
在每个成员放置时,最大的对齐数决定了结构体的大小——必须为最大对齐数的整数倍
现在回到上方图片的例子,第一个成员为 int ,四个字节,对齐数比较后为 4 ,
放偏移量为0 的位置,往后四个字节被使用,放置第二个成员 char 对齐数比较后为 1,直接放在偏移量为4的地方。
最大的对齐数为 4 ,现在大小为 5, 因此后面的三个字节也被使用来到偏移量为8的地方,结束
数组的处理
结构体中有数组时,这个数组有几个元素,就相当于把该数组分成 n 个同一类型的成员,最大对齐数由其中一个成员得到。比如结构体中一个 int arr[10];,就相当于放了 10 个 int 在内存中,对齐数也就为 8 ;对于 char 数组,指针数组,都同理可得的。
嵌套结构体的处理
在结构体成员中有了另一个结构体,这时又怎么来计算大小呢:
在之前的结构体中插入了一个字节大小为16的结构体,可以看见新的结构体大小为24.
计算结构体的嵌套问题时,要知道成员中的结构体内部的所有成员的大小和最大对齐数,本图中 over 内部的最大对齐数由 double 决定,是 8 ,
那样,该结构体被当作成员时,它的对齐数就是 8 ,要放在偏移量为 8 的整数倍的地方
内存对齐的意义
为什么不能直接把所有成员挨个放呢
原因有两个
1.平台问题,有的平台不能取出挨个排放的成员。
2.性能问题,不对齐的化,可能要取两次才能得到一个值,效率会下降。
默认对齐数的修改
使用#pragma预处理指令可以改变编译器的默认对齐数 格式如下
#pragma pack(n) // 设置默认对齐数为n
#pragma pack() // 还原为初始设置
五、结构体传参
函数中最好别直接放结构体,因为结构体往往大小都很大,直接传的话,函数内部建立的形式变量占很大的空间,不利于内存的节约,
所以结构体的传参一般传结构体指针,不用再建立一个临时变量,大大节约空间
六、位段(未完成)
结构体的成员不一定就会完全使用他们的空间,比如放入一个 char 成员,但我只用它来存 1-10
这肯定用不到一个字节,有着一定空间的浪费,这时位段就能很好的解决问题。
使用方法
位段的成员要求很苛刻,必须为整型家族类型,浮点数不行
在结构体中使用,通过 内部成员 : 位段数 ; 的方法使用,如下
位段中的 位代表着二进制的位,就是该成员会占用几个二进制位——比特。
大小计算
位段的大小计算依据平台不同,有着不同的算法,在此,用 vs 来演示。
首先先把原类型的字节占用放在那,之后从高地址处使用字节,当剩余比特位不够放下位段时,就向后再开辟一段原类型大小的字节。是不是很抽象,结合例子好明白一些。
上图中,先是 int 类型的 a1 开辟了32 比特的空间,它从高到低 ,用了 16个比特,之后轮到了 a2,还剩的16比特刚好放下了 a2 。然后 char 类型,开辟8 比特的空间,两个成员各用四个比特,占满了 。
弊端与好处
位段的实现与平台有很大的关系,每个平台不统一,使用了位段的代码的跨平台性大大下降;
位段在网络协议中有着重要的作用,帮助节约了很多的空间。
这边写不动了,位段这部分写的很草率,之后有精力了在完善它








