目录
什么是自定义类型
我们之前学过很多种类型,有int、char、float、double等等,这些都是属于既定类型,那么自定义类型顾名思义就是可以由你自己来定义的类型,比如我们接下来要讲的结构体、联合体、枚举都属于自定义类型。
结构体
首先我们来介绍结构体。
结构体的字面上的意思就是一个成结构的体系,因此它当然离不开组成它的部分了,而这些部分,就是结构体里边各种类型的成员。
结构体的声明
常规结构体的声明形式
我们这里创建了一个以学生信息为组成部分的结构体,我们可以看到一个简单的结构体是由哪些方面组成的。
①首先结构体的创建是由这样一个形式开始的,即struct+结构体类型名,而这里,结构体类型名就是stu(这个名字是自定义的,可以叫a也可以叫b,由于我们要创建一个关于学生的结构体,所以我选择了stu,即student的缩写,作为类型名。而这个特点就是我们标题所说的自定义类型的体现)
②接着我们还可以看到,在这个结构体的内部,包含了多个信息,即学生的姓名、年龄、班级、学号(如果需要的话,你可以再多加些信息,比如年级、性别等等)。这些信息的类型有char、int。当然,如果你愿意,你也可以在里边创建其他类型,比如double、float,甚至还可以再创建一个struct+类型名,形成结构体的嵌套,而这是后话了。
③细心的你可能还发现了,在这个结构体的最后,多出来个分号,而这个分号是不能缺少的,这似乎表明了在 };之间还可以有东西放进去,没错,在结构体的最后,还有结构体的变量,如图
初学易错点:
有人可能会觉得奇怪,为什么在创建结构体的开头已经有了stu,后面却又可以额外加s1,s2,s3。之所以会有这样的困惑,是因为他们习惯性地把我们之前创建变量的直观思维代入到了结构体上,简单来说就是,我们之前创建变量的形式是这样的:int a 即:类型+变量名,而我们又很容易把这样的形式套到结构体上,从而以为struct stu也是类型+变量名,但实际上这里的stu才是类型,也就是说这里stu的地位其实就相当于int,那结构体里边的变量在哪呢?那很显然就是那些加在后面的s1,s2,s3.
但是话说回来,像我们开始时如下图这样创建的时候:
这里并没有设定像s1,s2,s3这样的变量,而是什么都没有,};之间只是空着,那么这样是否违背了语法呢?其实并没有。 如图:
这里我们可以很直观明了地看到,像s1,s2,s3这样的变量可以之后创建。这说明在};之间先不创建变量,在后面需要时再另外创建变量是符合语法的。但这里有着全局变量与局部变量的区别,使用时要特别注意这个细节。
特殊的结构体声明形式
匿名结构体:
struct { int a ; char b ; float c ; } x ; struct { int a ; char b ; float c ; } a [ 20 ], * p ;
在这里,结构体的声明省略了类型名,并且这样做是符合语法的,这样的结构体叫做匿名结构体,由于没有类型名,它在之后不能再用来创建局部变量,所以这样的结构体一般只能作为一次性消耗品。
匿名结构体的重命名:
如图,利用typedef可以将匿名结构体重新命名,使其可以利用新的类型名去创建变量,但是要注意,虽然同样是放在};之间,但上图中的stu是类型名!
注意事项:
1)在创建匿名结构体的时候,不能再像创建常规结构体那样,把变量名省略,像这样:
虽然这样的创建方式在编译时不会报错(可能会警告),但是这样的结构体是没有用的!因为在创建时,它本身省略掉了类型名,而现在如果还要把变量名省略,那么它在之后也不可能再创建变量了,也就是不可能像下图这样:
道理很简单,虽然这张图里在创建结构体时省去了变量名,但是仍然可以凭借stu这个类型名在之后创建局部变量s1,s2,s3。但这对于匿名结构体来说却是不可能的,因为它连类型名也没有。
关于一些错误用法
一般来说,不同类型的结构体如果设定了相同的变量名,那么就会出现重定义,从而导致编译器报错,就像这样:
但是匿名结构体并没有定义类型名,那是不是就可以看作同一类型的结构体,从而定义相同的变量了呢?
答案是不行!
结构体的自引用
什么是结构体的自引用
结构体的自引用,简单来说,就是在结构体的内部,包含一个和该结构体类型相同的成员。
(在结构体内部再创建一个结构体,并且两者要类型一致)
浅谈单链表
为什么要提到链表呢?因为最能直观体现出结构体自引用的就是单链表,我们不要被这个抽象的表达给唬住,单链表的原理十分简单。
我们在上文有说,结构体之内其实可以再创建结构体,利用这个原理,搭配指针,我们就可以实现链表的创建。
如图:
结构体变量的定义与初始化
方法一:
方法二:
方法三:
结构体内存对齐
一般情况:
本环节是理解结构体的储存原理的关键,掌握后你就会明白结构体大小计算的规律。在开始之前,请大家先思考一下,下图输出的结果会是什么?
我想很多人的回答通常都会是:6 6 13(char、int、double的字节大小分别为1 4 8)
但是我们运行一下,出来的结果却是这样:
很反直觉对吧?要搞清楚这个问题,我们就要了解结构体内存对齐这个重要性质。其实这个知识点并不复杂,针对上图的练习3,下面一张图带你掌握:
小技巧:让占用空间小的成员尽量集中在一起,这样做可以在节省时间的同时最大限度地减小空间的浪费
修改默认对齐数
#include <stdio.h> #pragma pack(8) // 设置默认对齐数为 8 struct S1 { char c1 ; int i ; char c2 ; }; #pragma pack() // 取消设置的默认对齐数,还原为默认 #pragma pack(1) // 设置默认对齐数为 1 struct S2 { char c1 ; int i ; char c2 ; }; #pragma pack() // 取消设置的默认对齐数,还原为默认 int main () { // 输出的结果是什么? printf ( "%d\n" , sizeof ( struct S1 )); printf ( "%d\n" , sizeof ( struct S2 )); return 0 ; }
结论:
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数