前言
大家好,本文主要深度讲解关于结构体的使用及细节,收录到C—语法
专栏,此专栏定期更新C语言语法方面的知识,都是比较详细的,自己复习的同时也希望能帮助到大家,如果有不对或者不足的地方欢迎评论区补充。
🏡个人主页:悲伤的猪大肠9的博客-C语言领域博主
✨如果文章对你有帮助记得点赞收藏关注哦!!✨
🌈重要的不是成功,而是奋斗的过程
一、结构体
之前我们学过整形(short,int),浮点型(float,double),字符型,还有数组(存储相同类型的数据),但在实际问题中,这些类型显然不够,如果我们想表示一个学生的信息,就会有姓名、年龄、分数。。。这时候就需要一个可以存储不同类型数据的数据类型,结构体就此诞生。
1.结构体的声明
//第一种方式 struct student { char name[20]; //结构体中可以放任意类型 int age; float score; }; //分号不能忘 //第二种方式 在结构体末尾声明结构体实例 struct student { char name[20]; int age; float score; }s1; //s1是全局变量,此时可以根据s1调用结构体 //第三种方式 不声明结构体名称,使其成为匿名结构体 struct { char name[20]; int age; float score; }s1; //此时只可以使用s1来调用此结构体
2.结构体自引用
- 结构体是否可以包含自己本身?
//错误示范 struct Node { int data; struct Node next; }; //如果真的可以这样,那sizeof(struct Node)是多少? 岂不是无限大?
那既然这样不可以,那怎么让一个结构体包含自己本身呢。
//正确方法 struct Node { int data; struct Node* next; };
包含结构体指针,这样指针大小是4/8(32/64平台)个字节,就不会出问题.
3.结构体变量的定义和初始化
struct student { char name[20]; int age; float score; }s1; struct student s2; //定义结构体变量s2 struct student s3 = {"ahunb",19,100.0}; //定义s3的同时赋值
嵌套结构体初始化
struct child { char name[20]; int age; struct pet; } struct pet { char name[10]; int age; } struct child c1 = {"ahunb",19,{"wangcai",1}};
4.结构体内存对齐
掌握上面知识其实结构体基本使用应该没问题了,但结构体的对齐才是此篇文章的重点,让我们更深入的了解结构体变量。
有这样一个结构体,猜猜他的长度是多少
struct dog { char isWrite; int age; char isBlack; }; int main() { printf("%d",sizeof(struct dog)); }
答案是12,why?
重点
※结构体在内存对齐的规则:
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
比如char a[20] 数组,在结构体中的对齐数是1,数组按照数组类型来算,相当于20个char
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
内存对齐存在的原因:
- 不是所有硬件平台都能访问任意地址数据的,一些平台只能在某些地址取某些特定数据。
- 如果没有对齐,会产生两次内存访问,而对齐的只需要进行一次。
结构体内存对齐其实就是那空间换时间
回过头来看上面的题
struct dog { char isWrite; //默认对齐数为4,char对齐数是1,取其小,从1的倍数地址开始占一格 int age; //默认对齐数为4,int对齐数为4,取其小,从4倍数地址开始占4格 char isBlack; //默认对齐数为4,char对齐数是1,取其小,从1的倍数地址开始占1格 }; int main() { printf("%d",sizeof(struct dog)); //结构体大小为最大对齐数的整数倍,最大对齐数是4,12 }
默认对齐数可以通过#prama pack( ) 修改
#include <stdio.h> #pragma pack(1) //设置默认对齐数为1 struct S1 { char c1; //char对齐数1,默认1,对齐数1 int i; //int对齐数4,默认1,对齐数1 char c2; //char对齐数1,默认1,对齐数1 }; #pragma pack() //取消设置的默认对齐数,直接()里什么也不加,还原为默认 int main() { printf("%d\n", sizeof(struct S1)); //数组大小是最大对齐数的倍数,最大对齐数是1,所以总大小是6 return 0; }
5.结构体传参
结构体传参最好采用指针传参,不然进行压栈的时候要压入出整个结构体,会浪费很大一块空间,采用指针传参只会浪费四个或者八个字节的空间。
6.位段
位段声明与结构体类似,不过位段的i结构体成员必须是int,unsigned int或者sign int
- 位段可以很好的节省空间,但是会有跨平台的问题存在。
- 位段的声明:
struct a { int a:3; int b:4; int c:5; int d:4; }; //如果上一个字节的bit位不够存放下一个变量,直接舍去此字节剩余的bit位,从下个字节开始存储,此端数据共为三个字节
:前是位段名,:后是bit位
例如_a只有两个bit位,就只能存两个bit的数据,有四种可能,00,01,10,11
如果超出此空间,多余的会舍去。
- 给上面位段赋值,看看在内存中是怎样存储的
struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4;
位段的应用
学过计算机网络的小伙伴应该都认识这个,这是一个报文头,在网络中进行传输信息对空间要求非常苛刻,每天数不清的数据在网络中进行传输,如果没有位段这种方式,那么会多浪费很多空间。
完结
创作不易,还请各位小伙伴多多点赞👍关注✨收藏⭐