目录
什么是自定义类型
结构体
结构体的声明
常规结构体的声明形式
特殊的结构体声明形式
匿名结构体:
匿名结构体的重命名:
注意事项:
结构体的自引用
什么是结构体的自引用
结构体变量的定义与初始化
方法一:
方法二:
方法三:
结构体内存对齐
结构体传参
位段
枚举
枚举的声明
枚举的优点
枚举的使用
联合体
联合体的声明
大小的特点:
大小的计算:
什么是自定义类型
我们之前学过很多种类型,有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) // 设置默认对齐数为 8struct S1{char c1 ;int i ;char c2 ;};#pragma pack() // 取消设置的默认对齐数,还原为默认#pragma pack(1) // 设置默认对齐数为 1struct 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 ;}
结论:
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
结构体传参
struct S{int data [ 1000 ];int num ;};struct S s = {{ 1 , 2 , 3 , 4 }, 1000 };// 结构体传参void print1 ( struct S s ){printf ( "%d\n" , s . num );}// 结构体地址传参void print2 ( struct S * ps ){printf ( "%d\n" , ps -> num );}int main (){print1 ( s ); // 传结构体print2 ( & s ); // 传地址return 0 ;}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论:
位段
位段的声明和结构体是类似的,有两个不同:
位段的成员必须是 int、unsigned int 或signed int 。
位段的成员名后边有一个冒号和一个数字。
如图:
A就是一个位段。
计算:位段是按比特位的大小来进行计算的,比如一个字节有8个比特位,存入的时候根据数据类型的不同计算出相应的比特位依次往后存储,若存储不下则开辟下一个字节
举个例子:
那么8这个数字是怎么来的呢?是这样的:
int 型具有四个字节,共32个比特位,故存储时将前三个放入第一块空间中,由于第四个是30个比特位存储不下,因此开辟下一块空间存储,即开辟了两块空间,就是好8个字节。
注意:
和结构体相比,位段可以达到同样的效果,但是可以很好地节省空间,但是有跨平台的问题存在。
跨平台问题:
1)int 位段被当成有符号数还是无符号数是不确定的;
2)位段中最大的数目不能确定。(16位机器最大16,32位机器最大32)
当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,不确定。
枚举
枚举的声明
枚举顾名思义就是一一列举。把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
这里就可以使用枚举了。先上代码:
enum Day // 星期{Mon ,Tues ,Wed ,Thur ,Fri ,Sat ,Sun};enum Sex // 性别{MALE ,FEMALE ,SECRET} ;enum Color // 颜色{RED ,GREEN ,BLUE};
enum Day、enum Sex、enum Color都是枚举类型。
而{}里边的是枚举的可能取值,叫做枚举常量
这些可能取值都是有值的,默认从0开始,一次递增1。
当然,在定义的时候也可以赋初值。像这样:
enum Color // 颜色{RED = 1 ,GREEN = 2 ,BLUE = 4};
枚举的优点
我们可以使用 define 定义常量,为什么非要使用枚举?
原因如下:
1. 增加代码的可读性和可维护性
2. 和define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
枚举的使用
enum Color // 颜色{RED = 1 ,GREEN = 2 ,BLUE = 4};enum Color clr = GREEN ; // 只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。clr = 5 ; //ok??
只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
联合体
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
// 联合类型的声明union Un{char c ;int i ;};// 联合变量的定义union Un un ;// 计算连个变量的大小printf ( "%d\n" , sizeof ( un ));
联合体的声明
大小的特点:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员。
union Un{int i ;char c ;};union Un un ;// 下面输出的结果是一样的吗?printf ( "%d\n" , & ( un . i ));printf ( "%d\n" , & ( un . c ));// 下面输出的结果是什么?un . i = 0x11223344 ;un . c = 0x55 ;printf ( "%x\n" , un . i );
大小的计算:
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1{char c [ 5 ];int i ;};union Un2{short c [ 7 ];int i ;};// 下面输出的结果是什么?printf ( "%d\n" , sizeof ( union Un1 ));printf ( "%d\n" , sizeof ( union Un2 ));
以上就是我对自定义类型的相关讲解,讲解有不清楚或者讲错的地方,可以在评论区或者私信.看完记得点赞哦!!!