一、前言
我们之前学习过定义一个整型类型的变量又或者定义一个浮点型类型的变量...,这些变量可以描述一个整数又或者描述一个小数...,可无论是整数还是小数...,它们也只是简单对象。如果我们想要描述像一本书,一个人这种复杂对象,用我们之前学习过的类型去描述是完全不够的。一本书包含着很多信息,如书名、价钱、作者、出版时期...。我们会发现书的其中一个信息的表示我们是学过的,我们也能将其描述出来,如书名啊,作者啊,可以用字符数组,价钱啊,可以用整型...。C语言为我们提供了一种自定义类型结构体,并我们提供了一个声明结构体类型的关键字:struct。二、如何正确使用结构体?
1.结构体的使用(声明+创建变量+初始化)
小结:创建变量的同时,给成员 ’赋值‘ 的同时,才叫初始化,且可以对全部成员进行’赋值‘;而赋值不可以同时操作结构体的全部成员。2.匿名结构体
匿名结构体:省去结构体标签的结构体类型叫做匿名结构体小结:由上面两张代码截图可知,匿名结构体若不被关键字typedef重定义,则只能使用一次(在声明的同时,也进行变量的创建)。3.结构体自引用
在声明结构体类型时候,包含一个类型为自身的成员,可不可以?下面我将拿将对上面的People类型的结构体,增加一个成员,让它用来描述伴侣;第一个代码例子,类型于我们平常所说的无限套娃——结构体包含结构体,而这种做法,导致的是结构体的内存无限大,无法计算结构体的内存,所以这点,C语言是明令禁止的。第二个代码例子,我用一个结构体指针,若有伴侣了,我让它指向下一个结构体的地址,若无伴侣,则就填NULL;而指针类型的大小只与平台有关,64位平台8个字节,32位平台4个字节。所以第二代码例子,则是正确的自引用方式——包含自身结构体的指针。4.结构体的两个操作符
那我们又该如何逐一去访问该结构体的成员呢?其实C语言为我们提供了两个操作符(.)/(->);当我们知道了结构体的变量的时候,用操作符(.)当我们知道了指针结构体地址的指针变量的时候,用操作符(->)小结:(*xcf.girl).name <-----> xcf.girl->name三、结构体的内存对齐
1.什么是内存对齐?
结构体占内存中占据的字节并非是简单的内存成员类型大小的标量和,而是存在内存对齐这一规则。1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处;2.其余的成员对齐到,(某个数字)对齐数,的整数倍处;对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值VS默认对齐数为8;Linux中gcc没有默认对齐数,对齐数就是成员自身的类型大小。3.若存在嵌套结构体的情况,则结构体成员对齐到,自己成员中最大对齐数的,整数倍处;4.结构体最终内存的大小,是结构体中最大对齐数的整数倍。结构体中每一个成员都有对应的对齐数,对齐数最大的则为最大对齐数。2.为什么存在内存对齐?
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。性能原因:数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。小结:内存对齐是牺牲时间换空间的一种做法3.修改默认对齐数
四、位段
1.位段的概念
当我们学习完结构体后,就不得不了解下位段这一概念,何为位段?结构体的声明与位段的声明是相似的,换而言之,位段的实现是建立在结构体的概念上面的。位段与结构体有两点不同:位段的成员类型只能是整型;(如int,unsigned int,signed int,short,char等);位段的成员变量后面加上冒号与数字;(是位段与结构体最主要的区别),其中数字代表该变量在内存中所占据多少比特位。注:位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。下面代码就是位段的声明举例,至于创建变量和初始化,和结构体一样2.位段的内存分配
位段一次性开辟内存的大小是按照所需要,开辟char、short、int大小的字节。位段最终的内存大小,是最大对齐数的整数倍。不同平台上,位段的内存分配不同,也就导致了相同位段,不同平台的内存大小不同;3.位段的跨平台问题
不同平台上,int是无符号整型,还是有符号整型是不确定的——VS上面int是由符号整型一次性开辟的内存空间是从左向右使用(从低地址->高地址),还是从右向左使用是不确定的——VS上面内存空间的使用是从右向左的;剩下的内存空间不够下一个位段成员的填充,是否浪费是不确定的——VS上面是选择浪费的下面是三个例子:(是我查找各种资源+咨询+寻找典型的例子,才得到的结果)4.位段的注意事项
由上面三组代码的三个内存布局的研究,我们可以知道,位段成员的起始位置不是某个字节的地址,而是莫个bite位的地址。我们在指针篇了解到地址是内存单元的编号,也就是一个字节的地址,而一个bite位是没有地址的。所以位段的成员是没有地址的,也就不能&操作,进行scanf输入。所以我们只能通过临时变量去赋值给位段成员。