结构体:
结构体的声明
结构体的成员可以是不同类型的变量,在声明结构体的时候,并没有实际创建一个结构体,而是相当于一份设计图,用来说明该结构体是怎样设计的,结构体里有哪些成员等等。
struct stu { char name[20]; int age; float sroce; }; //这样就完成了结构体的声明,stu是这个结构体的标签,代表着这个结构体的设计类型 //结构体的创建 struct stu { char name[20]; int age; float sroce; }s1, s2; int main() { struct stu s3, s4; return 0; } //s1,s2,s3,s4都是我们创建的结构体变量,其中s1和s2是全局型变量,在同一源文件内,任何函数内部都可以直接调用,s3和s4是局部型结构体变量,只能由main函数内部直接调用,其他函数要想用需要传参过去。 //如果嫌struct stu 太长,可以用typedef 将struct stu 换个名字 typedef struct stu { char name[20]; int age; float sroce; } stu; //利用 typedef 将struct stu 的名字换成stu。 以后stu就相当于struct stu
还有一种结构体的声明叫匿名结构体,匿名结构体是没有标签名的,因此这种结构体只能够被创建使用一次,匿名结构体的使用场景适合只需要使用一次该结构体类型,看下面的一个例子。
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p //上面两个结构体都省略了结构体标签,表明它们都是匿名结构体. //但是 p = &x;是错误的,尽管两个结构体的内部设计是一样的,但因为没有标签,编译器会识别为这是两个不同类型的结构体,这也是为什么匿名结构体只能使用一次。
结构体大小的计算
要结构体大小的计算需要先了解结构体内存对齐的规则
1.第一个结构体成员在结构体起始地址偏移量为0处
2.其他结构体成员变量要对齐到某个数字(对齐数)的整数倍地址处
“对齐数” = 编译器默认的一个对齐数与该成员自身大小的较小值(vs默认的对齐数为8)
3.结构体的总大小为最大对齐数(结构体成员的对齐数)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
下面举一些例子
struct s { char i; int a; char b; }m; //计算m所占空间的大小
故该结构体的大小为12个字节
struct S2 { char c1; char c2; int i; }m; //计算结构体m的大小
故此结构体的大小为8个字节
存在内存对齐的原因
从上面结构体遵循内存对齐原则计算大小可知,有一些的内存是没有被使用到,造成内存浪费的,那么为什么不依次存放结构体的成员,而要整一个内存对齐的规则呢?
1.平台移植
并不是所有的硬件都能够访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定的数据类型,如果结构体不按照一定规则存放会造成程序移植后某些硬件无法访问其内存中的数据,其中通过预处理指令 #pragma()可修改默认对齐数,前面说到VS的默认对齐数为8,这是可修改的,修不修改取决于程序的用途。
2.性能提升
在32位机器上,内存对齐是更有利于读取数据的,如果不进行内存对齐,在读取结构体里某个数据时可能需要读取更多次,内存对齐有空间换时间的作用。
位段:
位段的声明
位段的成员必须是 int, unsigned int ,signed int 或者是 char
位段的成员名后边有一个冒号和一个数字
struct m { int _a:2; int _b:5; int _c:10; int _d:30; }; //m就是一个位段类型
位段内存空间的分配
位段的空间是按4个字节 (int )或1个字节(char)来开辟的
因为位段的不确定性因素较多,所以位段是不具有跨平台性的,需要移植的程序尽量少使用位段,下面有一些例子说明位段的内存分配
struct S { char a:3; char b:4; char c:5; char d:4; }s; //求位段s各成员的内部分配
首先开辟一个 char 类型的空间,位段成员变量a占用3个比特位,接着位段成员变量b再占用4个比特位,此时第一个char空间只剩下一个比特位了,显然放不下要占5个比特位的位段成员变量c。因此需要再开辟一个char类型的空间,用来放位段成员变量c,但此时第二个char类型空间只剩下了3个比特位,是放不下要占用4个比特位空间的位段成员变量d,因此要再开辟第三个char类型的空间,用来存放位段成员变量d,最终该位段类型占用3个字节大小的内存。
位段的作用
位段看起来是把一份char 或int 类型的空间裁开分为几份使用,那么什么样的情况下会用到位段呢,这里介绍一种常见的用法。
看上图的数据报,如果将上面每一个框框的数据都分配一个 int 类型来存储,可想而知,这样的空间消耗是十分巨大的,数据传输会很慢,而将4位版本,4位首部长度,16位标识等放到一个int类型里存储,这样就用到了位段,大大节省了空间消耗。
枚举
枚举的定义
枚举就是把可能的取值一一列举,枚举内部的成员都是可能的取值,也叫做枚举常量。枚举常量如果没有赋初始值,则默认第一个成员取值为0,第二个成员取值为1,依次递增。当然也可以给每个成员赋初始值。
enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; //这里Mon的值为0,Tues的值为1,依次递增,创建一个枚举变量,该变量的取值只能为枚举成员之一。 //enum Day s = Mon; s的取值只能为枚举内的某个成员 enum direction { right = 77; left = 75; up = 72; down = 80; } //当然我们可以在定义枚举时,给每个成员赋初始值,上面方向的枚举就是游戏设计中常用到的取值,每个方向的取值通过ASCII码对应着键盘上的某个字母。
枚举的优点
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
联合(共用体)
联合的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
因为共用同一块空间,因此联合体的大小至少是最大成员的大小,这样才能放得下最大成员
union Un { int i; char c; }; union Un un; //描述un联合体的空间分布
联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
typedef union Un1 { char c[5]; int i; }un1; typedef union Un2 { short c[7]; int i; }un2; int main() { printf("%d %d \n",sizeof(un1), sizeof(un2) ); return 0; } //计算结果为 8 16
原因:对于un1来说,char c[5]是成员内最大的,连续占了5个字节的空间,int i 和char c[5]共用这5字节大小的空间,但联合最终的大小要遵循对齐规则,char c[5]的每个成员都是char,因此其对齐数为1,int i的对齐数为4,那么成员内,最大对齐数就是4,最终大小是最大对齐数的整数倍,现在的大小为5,不是4的整数倍,因此,要再补3个字节达到对齐,最后大小就是8,un2也是同理。