我们之前学习过数组的使用,数组是相同类型数据的集合。那么对于不同类型的数据的集合又该如何?例如一个人的信息可能有姓名,性别,年龄,电话号码等等,我们想把这些不同类型的数据放在一个变量中,这就引出了结构体的概念。
结构体变量的关键字是struct。
我们举一个用来存放学生信息的结构体的例子:
1创建结构体变量第一种方式:
在创建结构体(声明)时,直接创建好结构体变量。
2.第二种方式:
先创建好结构体,后续需要时再创建结构体变量
像上面结构体中name,age,sex就是结构体成员。
在这里再介绍一种特殊的结构体声明:匿名结构体类型,也就是省略掉结构体的名称。
强调:这种结构体只能使用一次。
例如:
匿名结构体只能使用一次,因为我们没有结构体的名字,所以以后再想创建一个这样类型的变量是无法做到的。 在这里我们只能创建一个变量s。
1.按顺序初始化:
这样我们就创建了一个48岁,男人,zhangsan的全部信息。
注意:每个不同类型的变量之间要用逗号隔开。
2.指定顺序初始化
这里需要引入一个操作符:“.”操作符,它的作用就是帮我们找到一个结构体中的某个变量。
例如:
这样我们就按照自己想要的顺序初始化了s这个变量。
同时注意,以后我们需要修改s这个结构体变量中的信息或者访问信息时,我们依然可以通过“.”操作符。
2.这里想要访问结构体成员还有另外一种方法,通过->(结构体访问操作符)操作符。
1.“.”操作符的使用方法:结构体变量名.结构体成员名。
2.->操作符的使用方法:结构体指针->结构体成员名。
区别:点操作符是通过变量来访问结构体成员,而->操作符是通过结构体指针来访问。
当我们创建好一个结构体变量之后,我们肯定好奇这个结构体变量在内存中占了多少字节
简单看一个例子:
我们创建了两个结构体类型,里面所含成员相同,只是顺序不同,但是打印出的结果既不是我们感觉的(1+1+4),并且两次打印出的结果也不同。
这表明结构体类型在内存中的存储方式是有特殊规定的。
其实结构体在内存中存在对齐规则:
1.结构体第一个成员对齐到结构体变量起始位置偏移量为0的位置。
2.其他成员变量要对齐到偏移量为对齐数的整数倍的地址处。
对齐数=编译器默认的对齐数与该成员自身大小的较小值。
在VS中默认对齐数是8.
Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。
3.结构体的总大小必须是最大对齐数的整数倍,如果不是就要浪费多余的空间来凑。
4.如果嵌套了另一个结构体,被嵌套的结构体对齐到自己成员的最大对齐数的整数倍,结构体的整体大小是最大对齐数(包括被嵌套结构体中成员的对齐数)的整数倍。
特别注意:数组的对齐数按照单个元素来计算。
下面我们举一个例子来说明:
比如这个结构体
这样排好之后总大小是8个字节,最大对齐数是4,正好是其整数倍,所以这个结构体的大小就是8个字节。
再看一个成员类型与上述相同,但顺序不同的结构体
按照对齐规则放好之后,我们发现最大对齐数是4,但此时的总大小是9,并不是最大对齐数的整数倍,因此粉色的3个字节就是浪费掉的空间,最后这个结构体的总大小就是12(9+3)个字节。
打印看出与我们计算的结果相同。
这里我们可以得出一个结论:
为了让结构体尽量的节省空间我们应该把占用内存较小的成员集中在一起
#pragma是可以修改默认对齐数的。
这里我们把默认对齐数修改为1,也就意味着结构体中的所有成员都会连续存放,不会浪费空间。
注意:只能修改为2^n(包括1)。
#pragma pack()表示恢复默认对齐数。
这里再介绍一个宏offsetof(type(结构体类型),member(成员)):它的作用是计算结构体成员相较于初始位置的偏移量
使用前要先引用头文件
例如:
与我们上面推算的结果一致。
这里简单说明一下为什么要存在内存对齐:
我们都知道函数调用分为两种,传址调用和传值调用。所以结构体传参也是有两种方法。
可以看到,两种方式都可以达到我们想要的效果。
这里建议大家使用传址调用:
因为传址调用形参是一个地址最多也就4/8个字节,但是传值调用的话会将实参拷贝一份,所占用的大小一般都会比较大。因此为了节省空间,我们一般使用传址调用。
位段的声明与结构体类似,但有两点不同:
1.位段成员是int,unsigned int,char类型。
2.位段成员后面有一个冒号和数字,表示多少比特位。
所以位段是按比特位来分配空间的,它的出现就是为了节省空间。
这就是一次位段的声明。
注意:1.位段的空间是按照需要一次性以四个字节(int)或1个字节(char)来开辟的。
2.也不能对位段取地址,因为比特位是没有地址的。
3.不同的编译器对位段的内存分配有所差异。
在VS上:
1.在一个字节内部从低位向高位使用。
2.如果剩余的空间不够下一个成员使用就浪费掉,重新开辟一次空间。
位段的缺点:
位段有很多不确定的因素,因为不同平台的实现方式不同,不具有跨平台性,注重可移植的程序一个避免使用。
以上就是本节的全部内容,感谢观看!
请大家多多支持,点赞关注加收藏哦!