看完带你拿捏 C—结构体语法!全文无废话,真心换真心!

我们在写变量的时候,像int 、float 、double 、char这些已经写死了,int类型里面不能再写char类型了,那么不够灵活,比如通讯录、学生信息管理系统这些小项目,难道每次使用什么信息就要创建什么类型的变量?

为了让表示 :身份,姓名,年龄.........这些信息更加灵活使用,我们来认识结构体

什么是结构体

在C中定义的一种数据类型,它可以将不同类型的数据变量组合在一起,形成一个整体

 结构体可以存放多种数据类型,比如:学生姓名(char),年龄(int),身高(float).....

结构体的声明

比如:

我们来解释各个字符:

struct:表明这是一个结构体

student: 结构体名称(也叫“标记名”),自选自己定义

{  }”:表明里面的内容是一个整体

”:表明这个结构体声明结束

char Name、int Age、char Gender”:不同的变量集合(一个个变量组成)

结构体声明的位置

创建一个结构体,它的创建位置导致它发挥的作用域也不同

比如我创建在函数内部,那么它的作用域就只在这个函数内部:(声明在其它自定义函数同理)

 如果在函数外面声明,那么它在这个文件是可以发挥作用的:

 结构体的定义使用

struct student只是相当于类型,例如“int”“char”“double”这些类型

 那么我们知道类型+变量名+内容才算完成一个变量的创建,编译器也才初始化内容,给这个变量开创空间

同理,结构体也是如此,我们的声明相当于创建了这样一个类型给编译器,如果想使用,还需要附加 变量名(这个可以理解为  int  Age = 18;)

这里提一个简单快捷的声明使用方法:typedef 

typedef:可以理解为将名字简化的一个操作,例如:

struct student 被typedef修饰后,再在分号前面重新写一下结构名,以后就可以写成:student 

注意:结构体在使用时需要对准变量,什么意思呢?比如年龄就对应下面图中int类型的第二个变量,名字就对应图中char类型的第一个变量 

如果不对齐,那么后续使用的时候会出错,这点很重要(结构体使用的时候一定要对应变量类型)

 typedef与不加typedef在结构体中的命名意义不同

比如:

如果不加typedef,那么分号前面的  p2  就相当于已经创建了一个叫  p2  的结构体变量,可以直接用

如果加了typedef,分号前面的  p1  相当于结构体类型(p1 == struct student),再命名就可以使用了

 结构体的3种打印方式

                                                                直接打印

注意区分占位符的种类,用结构体名字.变量名就可以打印出来(注意struct student  是类型) 

                                                                 传值打印(不建议)

就是将  结构体变量名  作为参数传给函数,再用结构体接收就行了

                                                                 传址打印

注:(因为传值打印不推荐,性能不怎么好,所以我们一般选择传址打印)

传址打印:将结构体的地址传过去用结构体指针接收(因为是地址,所以要用指针接收)

2种传址方式打印:

                               1:指针解引用方式(注意:(*指针)拿到的是s1的地址,指针指向s1)

                               2:指针箭头指向方式

这2种打印方式的理解其实跟直接打印方式差不多,只是借助指针指向来找到结构体里面的元素 

 Const修饰  结构体  跟   结构体指针

const可以理解为一把锁,把内容锁定了,那么就不可以修改了

比如下面2种:(你只需要在想加的地方加上const就行了!理解也很简单,就是无法更改它修饰的内容)

这里需要注意的2点:(很重要)(注意  指针指向的地址  跟  指针指向内容  两者的区别)

 如果是在  *指针  左边   加const,那么表示 指针指向的内容不能修改,但是指针指向的地址可以修改

如果是在   *指针  中间   加const,表明指针指向的地址不能修改,但是指针指向内容可以修改

 结构体的特殊声明(从这里开始是进阶内容

什么是结构体的特殊声明?

就是没有  结构体名称  ,只有struct,例如:

这种特殊声明的结构体,可以叫匿名结构体,只能使用一次,原因是什么?(参考下面的图) 

第一次使用创建了一个这样的结构体变量

第二次使用就不能再创建这样的结构体变量了,因为地址发生了改变 

(比如两个匿名结构体,只是结构体变量名字不同,但是系统会认为这是2个不同的结构体变量)

     

 结构体的自引用

我们之前是不是有嵌套结构体的习惯,嵌套几个还好,但是如果要嵌套数量多了呢?如果还使用嵌套,那么就属于设计错误,不符合语法

我们先来看嵌套结构体:(一环套一环的)(这种写法以后慢慢改掉,养成好习惯!)

如果想结构体里面可以直接访问下一个结构体,那么我们有这么一个方法:

指针 指针的类型是下一个结构体的 类型,例如:

注意:最容易犯错误的地方就是 指针的类型(注意是下一个要指向的结构体类型) 

结构体的存储

结构体的存储相较于其它存储方方式,有很大区别,比如:

int类型的+char类型的数组不是等于4+5=9吗?

我们来学习结构体的存储:

规则:

           1:结构体的第一个成员,对齐到结构体在内存中的存放位置的0偏移处

           2:从第二个成员开始,每个成员都要对齐到(一个对齐数)的整数倍处

           3:结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍 

看不懂没关系,先给大家翻译2个陌生词:

偏移处:(就是自身到起始地中间的字节数)

 对齐数:(这个是数据存储时规定的,只要知道有这个名词就行了)

Vs编译器上规定默认对齐数为8 ,其它的对齐数题目会强调

 

 我从题目中给大家讲解怎么计算:

 我们先取一块内存空间,假设编号(地址)从0开始(所有找倍数的都是根据编号来找)

char c1  占一个字节,它是这个结构体的第一个变量直接放到第0个编号处,也就是0的位置

char c2 占一个字节,它是第二个变量(规定从第二个变量开始,要对齐一个较小对齐数的整数倍),那么我们知道Vs的对齐数默认是  8  ,8跟1(这个1代表它占几个字节)比较取较小值1,再在上一个变量存储的末尾开始(也就是从0编号的末尾开始),根据编号找1的倍数,如果是直接存放(直到找到为止)

int i  占4个字节,8跟4比较取较小值4再在上一个数据存放的末尾开始找4的倍数,找到后开始存放4个字节的大小

结构体的总大小:找结构体中变量类型最大的对齐数(我们假设是A),再从最后一个变量存储的末尾开始找A的最小倍数,这个倍数是几,结构体大小就是几

这是演示图:

总结:用编译器给的对齐数,跟变量类型占的字节大小比较,取较小值,再在编号上找这个较小值的倍数,再开始存放这个变量占的字节 个数

 再根据所有变量中最大的对齐数,从最后一个变量存储的末尾找它的倍数,最小倍数是几,结构体大小就是几

 我们来练习一题:

演示:

结构体偏移量的计算:offsetof    头文件:<stddef.h>

格式:  offsetof(结构体类型,变量名)

例如:(可以跟上图比较)

为什么要存在对齐数

1:平台原因

       不是所有的硬件平台都能访问任意地址上的任意数据的,某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常

2:性能原因

      为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问,仅需一次访问

总结:结构体的内存对齐是拿空间换取时间的做法 

修改默认对齐数

格式:    #pragma pack数字  或者  什么都不放

如果要恢复原来的编译器默认对齐数,则只需再次使用就行,括号里面  什么都不放

比如:

 这样再次计算结构体,就要按新设置的对齐数来计算,比如:

这样就将之前的对齐数改为了1,结构体的大小也就从之前的  16  变成了  13 

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值