C语言结构体
1.结构体的声明
要知道怎么计算结构体大小我们首先要了解结构体
1.1什么是结构体
结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量.
1.2结构体的声明和定义
structtag{
member-list;}variable-list;
member-list;
- 此处是结构体成员
- 可以有多个成员变量
- 成员类型可以是不同类型的变量
variable-list;
- } 之后的此处是定义的变量列表,在结构体创建初同时定义了一个结构体变量
- 此处定义的变量是全局变量
- 也可以在函数中 使用 struct tag abc; (tag为结构体名,abc为变量名) 方式来定义变量(局部变量)
例如此处描述一个学生:
structStu{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号}s4,s5;//分号不能丢intmain(){structStu s1;structStu s2;structStu s3;return0;}
此处的Stu就是一个结构体,内部成员描述学生的属性,s1,s2,s3,s4,s5都是定义的结构体变量.s4,s5是全局变量,s1,s2,s3是局部变量.
1.3匿名结构体
对于上述结构体的声明 有一种特殊的存在
struct{int a;char b;float c;}x;struct{int a;char b;float c;}*p;
细心的你应该发现了,上述的声明方式没有名称,这种类型叫做匿名结构体,由于没有名称,所以只会在声明结构体的时候,同时定义需要的结构体变量.
需要注意的是上述的两种结构体虽然成员内容相同,但是在内存中完全是不同的两个结构体.
所以基于上述代码
p = &x;
这样的代码是不合法的.
1.4结构体的自引用
structNode{int data;//1.struct Node next;//2.struct Node* next;};
如果结构体需要在成员中构建自己的变量的时候,应该使用 方式1 还是 方式2 呢.
如果你也觉得迷茫.那么请你看这么一段代码.
sizeof(structNode);
如果使用方式1的话,那上述代码算出来的结果应该是多少呢.
我们发现会进入无限循环,无法算出结果.这在计算机种有限资源的容器中是不允许出现的.所以方式2才会正确的方式.也就是下述代码.
structNode{int data;structNode* next;};
1.5结构体改名
结构体也是可以改名的(typedef关键字)
//结构体改名 typedefstructStudent{int id;char name[20];}Stu;intmain(){
Stu s1;return0;}
我们可以发现在改名之后,我们可以更方便的创建结构体变量.
改名后的自引用
知道结构体可以改名后,肯定有人要问 那我可不可以,使用改名之后的结构体名称来进行自引用.也就是下述代码.
XXX
//匿名typedefstruct{int data;
Node* next;}Node;//非匿名typedefstructNode{int data;
Node* next;}Node;
简洁明了,不可以.这是不允许的.想要自引用,只能使用原名称的指针类型来引用.
//这是正确的引用方式typedefstructNode{int data;structNode* next;}Node;
此块代码是正确的引用方式
2.结构体的初始化
structPoint{int x;int y;}p1;//声明类型的同时定义变量p1structPoint p2;//定义结构体变量p2//初始化:定义变量的同时赋初值。structPoint p3 ={4,5};structStu//类型声明{char name[15];//名字int age;//年龄};structStu s ={"zhangsan",20};//初始化structNode{int data;structPoint p;structNode* next;}n1 ={10,{4,5},NULL};//结构体嵌套初始化structNode n2 ={20,{5,6},NULL};//结构体嵌套初始化intmain(){structNode head;//定义后初始化
head.data=0;
head.p = p3;
head.next =NULL;
Node h2 ={2,NULL,NULL};//定义时初始化}
结构体初始化有两种
- 在定义的同时初始化
- 在定义之初不初始化,之后在对成员分别进行初始化
而在定义之后,对成员的操作方式也是有两种
- “.” 操作符:针对普通类型
- "->"操作符:针对指针类型
structdate// 声明一个结构体类型{int month;int day;int year;}structstudent{int num;char name[20];char sex;int age;structdate birthday;char addr[30];}student1,*student2;intmain(){//直接使用"."操作符进行操作.
student1.num =10;//bitthday成员也是一个结构体,可以嵌套使用"."操作符
student1.birthday.month =5;
student1.age =20;
student1.age ++;//student2是指针类型,使用"->"操作符
student2->age =18;//由于优先级问题需要带括号(&student2).num =31;}
当然指针类型也是可以使用".“操作符的.只需要解引用即可,但是注意需要带括号,因为”."操作符优先级高于解引用操作符.
3.结构体大小的计算
了解了结构体以及基本操作,那我们来看看结构体的大小应该怎么计算
3.1结构体对齐
结构体对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。(最大对齐数不包含vs默认值)
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
3.2结构体大小的计算
structS1{char c1;int i;char c2;};printf("%d\n",sizeof(structS1));
灰色为浪费掉的空间.
根据上述规则
- 首先比较c1的大小和默认对齐数的大小. 显然c1的小以为c1的大小为1.然后根据1和当前偏移量对比,如果偏移量为0,则从当前位置开始存入数据,如果偏移量不为0,将偏移值移动到1的倍数处.存入c1的值,0位置.
- 然后比较下一个,也就是 i 的大小和默认值得大小的最小值,得到的值为4.然后根据4和当前偏移量比较,不为0则移动到4的倍数处.存入i的值,也就是4位置.
- 然后比较下一个,c2的大小和默认值得大小的最小值,得到的值为1.然后根据1和当前偏移量比较,不为0则移动到1的倍数处.存入c2的值,也就是9位置.
- 最后计算总大小.成员的最大对齐数为4(不包含vs的默认值),当前偏移量为9,需要对齐为4的倍数.也就是12.所以结构体大小就是0到对齐位置之前的内存大小,也就是12.
所以结构体最终大小为12.
3.3补充
可以使用#pragma pack(8)来更改默认对齐数
//在创建结构体之前设置#pragmapack(1)//设置默认对齐数为1//使用完后可以使用无参数的来还原为默认#pragmapack()//取消设置的默认对齐数,还原为默认
版权归原作者 魚小飛 所有, 如有侵权,请联系我们删除。