
1. 引文C语言中已经有很多内置类型如charintfloatdouble等等但是只有这几个内置类型是远远不够的假如我们需要描述一个学生的基本信息这只靠一个内置类型是不行的。C语言为了解决这种问题增加了结构体这种自定义的数据类型让程序员可以创建适合的类型。结构是⼀些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量。2. 结构体2.1 结构体的声明C语言中我们使用struct来定义结构体不能直接用结构体名当类型需要加 struct。括号里面的就是成员变量我们用实例来分析struct是结构体的关键字Stu是结构体的名字大括号里面定义的变量是成员变量至于括号外面的s3和s4就是创建的结构体变量这种创建结构体变量的方法比较简洁如果这种创建方式是放到main函数里面的话就是创建的局部变量我们也可以使用其他的创建方法:注意无论是那种创建方法都大括号外面都必须用分号结尾。2.2 结构体变量的创建和初始化创建的话还是很简单的就是注意分号初始化有两种初始化的方式一种是按照结构体的成员变量的顺序来进行初始化这种方式十分简便还有一种就是不按成员变量的顺序初始化这种方式需要我们将被初始化的成员变量的名称也写上这里注意一下名称前面有一点这点千万不要忘记了初始化时我们可以不写结构体变量名但是我们在打印的时候必须写上结构体变量名。结构体是支持循环嵌套的。2.3 结构体成员的直接访问和间接访问我们前面的结构体变量.成员名这就是直接访问。有时候我们得到的不是⼀个结构体变量而是得到了⼀个指向结构体的指针。这时候我们就要通过间接访问。这种结构体指针-成员名这就是间接访问的形式注意这里是指针。2.4 结构体的特殊声明这种没有命名的结构体类型只能使用一次。但是如果要想长期使用我们可以对匿名结构体进行重命名使用typedef进行类型重命名注意并不是只有匿名结构体时才能使用typedef来进行重命名使用已有名称的结构体使用typedef来重命名可以省去每次创建结构体变量都要敲struct这里跟我前面的重命名方式有点不一样这是另一种方式但是我那种也没错。2.5 结构体的自引用自引用就是结构体里面包含指向自身类型的指针。注意只能放指针不能直接放结构体变量如果放结构体变量那就会无限套娃内存大小根本无法计算。放入指针指针的大小是固定的。结构体一般是结合typedef来一起使用的这是我们后续学习链表的基础。typedef在别名没有生效之前不能省略struct这也就导致匿名结构体不能进行自引用。2.6 结构体内存对齐我们已经基本掌握结构体的使用方法了接下来我们要讨论一个问题结构体的内存大小怎么计算?结构体有专门的对齐规则这些规则你光看是没有一点用的但是要记住配合案例理解性记忆我们直接上实践根据这个例子我们一步一步套公式分析为什么会是12和8明明结构体里面我们认为只有6个字节为什么将顺序换了一下之后结构体的字节大小也会变S1的内存分布图首先第一个成员对齐结构体变量起始位置偏移量为0的地方这个是固定的必须对齐c1是char类型占1个字节第二个成员首先要求对齐数我们使用的vs编译器默认为8i是int类型所以对齐数是8和4中的较小值也就是4然后找到对齐数的最小的整数倍的地方也就是4的那块空间int类型是占4个字节的所以从4开始填充到7的位置同样的道理c2的对齐数是1即找到1的最小倍数位置每一个都是1的倍数所以就在7的旁边这时候你数数也就只有9个字节的空间为什么输出结果是12呢这时候我们还有第三条结构体总大小等于最大对齐数的整数倍前面的c1c2和i中对齐数最大的是i最大对齐数是4我们已经9个字节空间了最近的4的倍数就是12了这也就是输出结果了。同理我将S2的图画出来你们试试推出输出结果来。2.7 为什么存在内存对齐的情况不知道你们有没有发现在我们在求结构体总大小时没有填充的空间都是属于浪费的我们为什么要浪费呢我们不是平白无故的浪费是用这点空间来换取时间上的加速。那我们怎么满足既要对齐又要节省空间呢我们前面的S1和S2中的成员变量只是因为顺序不一样就导致空间大小也不一样所以我们还是有方法使得既要对齐又能节省空间的。我们可以将占用空间小的放到一起。2.7.1 修改默认对齐数导致内存空间浪费的原因还有一个就是默认对齐数通过缩小对齐数可以减少填充压缩结构体占用内存。#pragma这是个预处理指令预处理后续会讲可以修改编译器的默认对齐数。结构体在对齐方式不合适的时候我们可以自己更改默认对齐数。2.8 结构体传参我们在结构体传参的时候尽量传结构体的地址可以节约时间和空间上的传统开销。为什么优先使用传址的方式比较节省空间和时间呢使用传址的方式传入指针的大小不是4就是8使用传值的方式既要重新创建一个结构体变量空间又很可能大于8。如果说你能思考到使用传址可能不小心将结构体的数据修改我们可以使用const来约束指针。3. 结构体实现位端位段是结构体中小众但是比较使用的一个技巧。3.1 什么是位段我们来看一下位段的实际形态位段中的_只是一种命名习惯没有说位段一定要用_位段中的“位”指的是bit位位段后面的数字指的就是成员变量占的bit位可以看到使用位段的方式大大减少了使用空间这时候聪明的你肯定会想到位段中的总共也就47个bit位啊为什么占了8个字节位段依托的是基础容器一个位段容器不能成员不能跨越两个容器空间不够就再开一个容器刚开始时一个int容器32个bit位_a_b_c17个bit位不够再将_d也装上所以说这时候我们就要再开一个int容器来存放_d2个int容器也就是8个字节空间。这时候聪明的你就可能有疑问那位段会不会里面的类型都一样并不是的位段允许混合不同类型的位段成员但是有一条关键规则相邻、同类型的位段才会放进同一个容器一旦类型切换立刻开启新容器。3.2 位段的内存分配位段有很多的不确定因素如果是跨平台最好不要使用位段3.3 位段的注意事项位段中的成员可能没有地址我们前面三个成员变量存放到一个容器中_d放不下就重新开辟一个容器但是前一个容器是还有空间的如果整个字节的起始地址就是那个没有使用的空间那存放到里面的成员是不是就没有地址所以我们不能使用来获取地址。4. 总结结构体是自定义类型中的一个重要模块也是干货最多的后续还有其他的自定义类型例如联合和枚举本文文章篇幅较长希望看到这里的各位能有所收获。