结构体
- 结构是一些值的集合,这些值被称为成员变量,结构的每个成员可以是不同类型的变量。
数组是一些值的结合,类型是相同的
结构体的声明
struct tag { member_list; }variable_list;//全局变量
- 这里通过前面的列表创建的变量是全局变量
typedef struct tag { member_list; }tag;//相当于struct tag
- typedef可以将复杂的类型简化
匿名结构体
struct { member_list; }variavle_list;//必须存在
- 匿名结构体类型,如果没有对结构体类型重命名,只能使用一次
结构体的自引用
//结构体的自引用 struct stu { int age; struct stu* next; };
typedef struct stu { int age; struct stu* next; }stu;
结构体的初始化
//结构体的初始化 #include<stdio.h> struct student { char name[10]; unsigned int age; char sex[5]; }; int main(void) { //初始化 struct student n1 = { "张三",21,"男"}; //打印 printf("%s %u %s", n1.name, n1.age, n1.sex); return 0; }
运行截图
结构体的内存对齐
//结构体的内存对齐 #include<stdio.h> struct eg1 { int i; char j; char k; }; struct eg2 { char x; int y; char z; }; int main(void) { //打印eg1 printf("%zd\n", sizeof(struct eg1));//8 //打印eg2 printf("%zd\n", sizeof(struct eg2));//12 return 0; }
- 结构体对齐规则
1.结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处
2.从第二个成员开始,每个成员都要对齐到(一个对齐数)的整数倍处
对齐数:
结构体成员自身大小和默认对齐数的较小值
在VS中:默认对齐数为8
Linux gcc:没有对齐数,对齐数就是成员自身大小
3.结构总大小为最大对齐数的较小值
4.如果结构体中嵌套了结构体成员,要将嵌套的成员对齐到自己的成员中最大对齐数的整数倍处
5.结构体的总大小必须是最大对齐数的整数倍,这里的最大对齐数是:包含嵌套结构体成员中的对齐数的所以对齐数中的最大值
- 结构体内存对齐的原因:
1.平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在地址处取某些特定类型的数据,否则会抛出硬件异常
2.性能原因:
数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因在于,为了访问来对齐的内存,处理器需要作俩次内存访问,而对齐的内存仅需要一次访问
总结:
结构体的内存对齐是拿空间来换取时间的做法(满足对齐,节省空间:让占用空间小的成员尽量集中在一起)
修改默认对齐数
#pragma pack()可以设置默认对齐数
//修改默认对齐数 #include<stdio.h> //修改默认对齐数为2 #pragma pack(2) struct eg1 { char s1; int s2; }; //恢复默认对齐数 #pragma pack() struct eg2 { char s1; int s2; }; int main(void) { //打印eg1 printf("%zd\n",sizeof(struct eg1));//6 //打印eg2 printf("%zd\n", sizeof(struct eg2));//8 return 0; }
总结:
结构在对齐方式不合适的时候,可以自己更改默认对齐数
结构体传参
//结构体传参 #include<stdio.h> struct eg { int arr[100]; char ch[20]; }s1 = { {1,2,3,4,5} ,"abcdef"}; //结构体传参 void print1(struct eg s1) { printf("%s\n",s1.ch); } //结构体地址传参 void print2(struct eg* ps) { printf("%s\n",ps->ch); } int main(void) { //结构体传参 print1(s1); //结构体地址传参 print2(&s1); return 0; }
运行截图:
- 总结:结构体传参的时候,要传结构体的地址
原因在于,函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以导致性能下降
位段
- 位段的声明和结构体的声明基本相似,但也存在俩点不同:
1.位段的成员必须为int,unsigned int或者 signed int
2.位段的成员名后面有一个冒号和一个数字
//位段 #include<stdio.h> struct eg { int _a : 2; int _b : 5; int _c : 10; int _d : 20; }; int main(void) { printf("%zd",sizeof(struct eg)); return 0; }
运行截图:
- 位段:二进制位,可以节省空间
位段的内存分配:
1.位段的成员可以是int,unsigned int,signed int或者是char (属于整数家族)类型
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
3.位段涉及很多不确定因素,位段时不跨平台的,注重可移植的程序应该避免使用位段
位段的跨平台问题:
1.int位段被当成有符号数还是无符号数是不确定的
2.位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27时可能在16位机器上出现问题)
3.位段中的成员在内存中从左到右分配,而且从右向左标准尚未定义
4.当一个结构包含俩个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
枚举
- 枚举:即一 一列举
//枚举 enum Day { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; enum Color { Green, Blue, Red, Orange };
enum Day和enum color都是枚举类型,{ }中的内容是枚举类型的可能取值,也叫枚举常量。这些枚举常量都是存在取值的,默认是从0开始,一次低递加1
- 也可以在定义的时候赋值
enum Day { Mon = 1, Tues = 2, Wed = 3, Thur = 4, Fri = 5, Sat = 6, Sun = 7 };
枚举的优点:
1.增加代码的可读性和可维护性
2.和#define定义的标识符比较,枚举由类型检查,更加严谨
3.防止了命名污染(封装)
4.便于调试
5.使用方便,一次可以定义多个变量
联合
- 联合同样也是一种自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共同体)
//联合 union eg { char i; int j; };
- 特点:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
- 联合大小的计算:
1.联合的大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数的时候,就有对齐到最大对齐数的整数倍
//联合 #include<stdio.h> union eg { char i; int j; }; int main(void) { union eg s; printf("%p\n", &s.i); printf("%p\n", &s.j); return 0; }