C语言自定义类型详细讲解
1.结构体
1.1.结构体的声明
1.2.使用方式
1.3.内存存储方式
1.3.1.内存对齐规则
1.3.2.举例讲解
2.联合体
2.1.联合体的声明
2.2.内存存储方式
3.枚举
3.1.枚举类型的声明
3.2.枚举的使用
4.完结撒花
创作不易,希望三连支持一下,拜托啦🙏🙏🙏qaq
1.结构体
1.1.结构体的声明
结构体是用于存放多种变量类型的类型,意思就是把多个不同类型变量存放在一个类型中,比如将int,char,float类型分别创建一个变量,然后把这些变量都放进一个结构体当中,再将结构体进行命名,之后就可以直接拿结构体进行变量的赋值等操作,结构体的定义要以struct来表示,我们看下图进行学习:
struct表示一个结构体,tag表示结构体的标签名,大括号里面表示成员列表,意思是进行类型变量的创建,最后是变量列表,意思是使用这个结构体类型所创建的变量。
天生我材必有用(虽然用在这里不太恰当【狗头】),对于结构体我们应在什么时候去使用呢?其实对于结构体的使用是非常多的,比如我们需要编辑记录一个学生的各项情况,因为所需记录的各项数据不一样,需要用到多个变量类型,这时就可以去创建一个专门的结构体用来记录,如下所示:
这样我们就创建出了一个结构体,结构体变量名为struct str,这里分别记录的是学生名字,年龄,性别,成绩,之后我们就可以拿定义的结构体去进行操作。
1.2.使用方式
上面我们创建出了一个结构体之后我们该去如何使用它呢?对于结构体的使用有两个方法一个是直接使用,一个是间接使用。
直接使用:
结构体变量名.成员名,.成员名
对于上图第141和142行就是使用结构体所创建的结构体变量,struct str就是所创建的结构体变量a1,a2的类型。a1是按照变量里的顺序进行赋值,如果不按照所创建的变量顺序进行赋值那么就需要按照a2所创建的那样用".“加变量名在赋值,这里的”."其实是叫结构体成员访问操作符,是一个操作符。
打印就按照145,146所打印的那样,输入所对应的占位符,后面指明所打印的对象即可。
间接使用:
结构体指针 -> 成员名
间接使用就是指对所创建的结构体变量取出其地址,从地址的角度进行访问操作。这里对于结构体指针p1要使用 -> 叫结构体成员间接访问操作符,用 -> 来指向结构体成员才能进行访问,这就使间接使用。
这里我们还需要强调一点,对于上面刚开始所讲的变量列表,如下图:
这里的a3,a4,就是在变量列表中创建的变量,也是所创建的结构体变量,也可以对其进行赋值:
所创建的a3,a4结构体变量与a1,a2的区别就是前者是全局变量而后者是局部变量。
1.3.内存存储方式
这里涉及到一个问题:计算结构体的大小。
也是很热门的考点:结构体内存对齐。
1.3.1.内存对齐规则
要想理解结构体等自定义类型是如何在内存中进行存储的话,我们首先要掌握结构体的对齐规则:
1.结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的对其数 与 该成员变量大小的较小值。
—VS 中默认的值为 8。
—Linux中 gcc 没有默认对齐数。对齐数就是成员自身的大小。
3.结构体总大小为最大对齐数(结构体中每一个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍数,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
1.3.2.举例讲解
对于上面的规则我们可能会有几个名词还不理解其意思,下面我们使用VS编译器通过画图举例来给大家一步一步讲解。
先看下图:
上图中是打算计算struct S1,struct S2结构体的大小,大家现在可以猜猜看这两个结构体的大小分别是多大。
下面我们公布答案:
正如上图所示,struct S1的大小为8个字节,而struct S2的大小为12个字节
明明创建的是相同的变量类型,只因创建顺序不同为什么大小也会不同呢?
这就要讲起结构体是如何在内存中开辟空间存储的了。
我们认真看下面这张图:
假设右边表示创建结构体在内存空间所开辟的内存,我们先拿s1讲解,红色箭头指向的就是代表结构体s1所开辟的空间,假设起始位置为红色箭头所指的位置,右边的数字就代表与起始位置的偏移量,顾图思义,偏移量就是指与起始位置所差的字节数,那么我们开始按照s1的成员对空间进行开辟,由对其规则1我们知道:结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处,所以红色箭头下面所标的蓝色的方块表示的就是char c1所占的一个字节,接着我们需要计算其对齐数,根据对其规则2:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的对其数 与 该成员变量大小的较小值。变量大小char c1为1(1字节),而在VS中编译器默认的对齐数为8(这是编译器自己设置的,不同编译器下可能会是不同),较小值为1,所以char c1成员变量的对齐数为1。我们继续开辟,而对于char c2,其与char c1一样,对齐数也为1,由对其规则2,其他成员变量要对齐在其对齐数的整数倍偏移量的位置,空间向下走偏移量为1是1的整数倍,所以橙色方块区域就是为char c2所开辟的空间。继续走,int i,其大小为4,与8相比小,所以其对齐数为4,而存放的偏移量需是对齐数的整数倍,所以空间往下,偏移量为2,3处的空间不能开辟,偏移量是4为对齐数4的倍数,所以对于int i的开辟就从偏移量为4处的位置开始,向下4个字节,图中连续4个蓝色的方块,表示的就是int i,所开辟的空间。到此我们对于结构体内部成员的空间开辟结束,数图中总共开辟的字节数是8,那么s1结构体真实的大小就真的是8了吗?不要忘了还有规则3:结构体总大小为最大对齐数(结构体中每一个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。在s1中,三个成员变量的对齐数分别是1,1,4,所以结构体的总大小应为4的倍数,而刚我们所数一共开辟了8个字节,就是4的倍数,所以s1结构体的总大小就是8字节。而对于偏移量为2,3处的空间是没有存储东西的,在结构体s1创建完成后,这两块空间相当于是浪费掉了。
下面大家可以根据规则试试s2结构体大小为12是如何来的,下面我们给大家进行讲解。
对于s2,其与s1的不同就是变量创建的顺序不同,下面我们还和之前讲的一样,将图中蓝色箭头所指向的位置当作内存开辟的起始位置,右边数字还是表示各个位置的偏移量,在s2中,char c1,其直接从开始位置进行开辟1个字节(如图中箭头下面蓝色方块便是),而其对齐数为1。接着int i,其对齐数为4,而偏移量为4才是其整数倍,所以就从偏移量为4的位置进行开辟4个字节(图中橙色的区域便是)。接着char c2对齐数为1,而任何数字都是1的倍数,所以就接着int i所开辟的空间往下进行开辟1个字节的空间,(图中绿色方块便是)。所有成员开辟完成后就进行总的结构体大小的计算,每个成员对应的对齐数为1,4,1,最大为4,所以总结构体大小需要是4的倍数,而对着我们刚才所开辟的空间,数有9字节,而9并不是4的倍数,这里离9最近的4的倍数是12,所以我们必须在多开辟出三个字节,一共12字节,总结构体大小就必须为12。而对于偏移量为1,2,3处的空间和最后多开辟的9,10,11处的空间也相当于是浪费掉了,并不会去使用这些位置的空间。
内存中结构体的开辟就是如此。
2.联合体
2.1.联合体的声明
像结构体一样,联合体也是由一个或多个不同类型的成员构成,区别在于在联合体当中,编译器只为最大的成员分配最够的内存空间。联合体的特点是所有成员共用同一块内存空间,所以联合体也叫:“共用体”,因为是共用一块内存,所以给联合体中的一个成员赋值,其他成员的值也会跟着发生变化。
对于关键字结构体为struct,联合体为union
2.2.内存存储方式
我们看下图进行学习:
上面就是对联合体的声明,与结构体是相似的,只要把关键字换成union即可。
我们认真观察主函数里的内容,定义了一个联合体变量uu,该联合体的大小为4个字节,且对于联合体变量uu里面c和u取地址,打印出来得到的地址是完全相容的,这就与我们上面所介绍的联合体相符合,事实也确实如此,联合体的成员在共用一块空间。
上图就表示联合体u中变量uu和c是在使用同一块空间。
如果改变c的值,整型变量uu的第一个字节内容也会被改变,uu的值就也会随之变化,所以给联合体中的一个成员赋值,其他成员的值也会跟着发生变化。
因为值会互相影响改变,所以我们在使用联合体的时候一般对于其里面所创建的类型变量不会同时进行使用。
结构体和联合体的空间内存对比:
这里也需要说一下,联合体看起来目的是为了节省空间,但在内存开辟上面也是存在对齐的,联合体的大小至少是最大成员的大小,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
我们看下图计算一下联合体的大小:
对于第一个Un1联合体其大小为8,不能直接就以为是5,因为其对齐数分别是1,4,虽然最大大小为5,但是要考虑到对齐,所以必须是4的倍数,大小就是8。
对于第二个Un2联合体同样道理,最大大小是14,而最大对齐数是4,所以大小为16。
3.枚举
3.1.枚举类型的声明
枚举顾名思义就是一一举列,把一个集合的所有可能的取值一一列举出来,比如在一周一共有7天,周一到周日可以一一列举出来,性别有男,女,未知也可以一一列举出来,对于这些数据的表示就可以使用枚举了。
我们看下图进行学习
上面就是对枚举的声明,我们枚举的是性别,枚举里面创建了三个:男生,女生,未知,对于在枚举里创建的成员我们叫做枚举常量,枚举常量在创建出来后都会赋有初始值,一般就是0,1,2,3…,图中直接将枚举常量进行打印出来便是0,1,2。
我们也可以改变枚举常量的初始值:
但是只能在枚举里面进行修改,枚举常量在外面是不可以被修改的
3.2.枚举的使用
枚举创建好后我们就可以去使用,如下图:
这就是用枚举常量给枚举变量赋值,而枚举变量只能取枚举常量,不能赋其他值,那么其作用其实就与#define一样了,但是使用枚举是有很多优点的:
1.可以增加代码的可读性和可维护性
2.和#define定义的标识符比较枚举有类型检查,更加严谨
3.便于调试,预处理阶段会删除#define定义的符号
4.使用方便,一次可以定义多个常量
5.枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。
4.完结撒花
如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步