前言:
何为结构体,结构体又是什么呢,相信有很多小伙伴对结构体还没有一个清楚的概念,今天咱也一起来探讨一下何为结构体,在C语言当中有着许多的数据类型,如char,int,long,double等等这些类型分别用来存放着相对应的数据类型,而在日常的需求当中,很多时候我们所要用到的类型都不止一个,这个时候多种数据类型,放在代码当中很容易就搞混了,此时C语言为了应对这种情况的发生,引进了一个名为结构体的概念,专门用来存储多种数据类型,将这些数据类型封装在一起,统一使用
举个例子:一个学校要存储一个学生的信息,那用代码应该如何存储呢
int main()
{char name[20] = "wangwu"; //姓名int age = 20; //年龄char speciality[20] = "yunjisuan"; //专业int ID = 5; //学号return 0;}
没学结构体前,只能一个个赋值,这样写虽然没有错误,但是数据一旦多起来,会显得很麻烦,下边我们来看一下结构体又是如何存储这些数据的呢
struct Student
{char name[20]; //姓名int age; //年龄char speciality[20]; //专业int ID; //学号
};int main()
{struct Student s = { "wangwu",20,"yunjisuan",5 };return 0;
}
此时关于学生相关信息的赋值一条语句即可搞定,后续这个结构体还可重复调用,比起前边没有运用结构体的语句是不是简单明了很多呢
下边我们来了解一下结构体在C语言当中应该如何正确的调用,赋值等
结构体的声明:
struct tag
{member-list; //数据列表
}variable-list; //变量列表
举个例子(这边还是以上边的代码举例):
struct Student
{char name[20]; //姓名int age; //年龄char speciality[20]; //专业int ID; //学号
}zhangsan,lisi,wangwu; //可以省略到后边主函数当中定义变量int main()
{struct Student s = { "wangwu",20,"yunjisuan",5};return 0;
}
相比之前的代码,这里在结构体结尾多了一个变量的初始化,但是上边并没有初始化变量,所以这里的variable-list是可以省略的,等后续需要的时候再去初始化
结构体的不完全声明:
相比上边的完全声明情况下,还有一组特殊的声明方式,下边我们一起来看看它特殊在哪里:
//完全声明
struct Student
{char name[20]; //姓名int age; //年龄char speciality[20]; //专业int ID; //学号
}zhangsan,lisi,wangwu;//不完全声明
struct
{char name[20]; //姓名int age; //年龄char speciality[20]; //专业int ID; //学号
}a,a1;
上边两段代码,有着一些细微的差异,我们仔细观察会发现,它们不同的地方,是struct后边一个有标签(tag)Student,一个后边啥也没有,而这个啥也没有的,就是我们要讲的不完全声明,那么问题来了,居然它没有标签(tag)那么我们在后续调用的时候又该如何调用呢,如果有多个不完全声明呢,这个时候我们就要注意,当它有多个的时候,编译器也不知道该调用哪一个,所以在使用不完全声明的时候,要确保你只会使用一次该声明的情况下去使用,否则可能程序会发生错误
结构体变量的创建及初始化:
变量的创建无非就是在主函数当中以结构体类型 ,创建一个变量,初始化则是在创建的同时给它赋值,下边我们来看一组例子:
struct Student
{char name[20]; //姓名int age; //年龄char speciality[20]; //专业int ID; //学号
}zhangsan,lisi,wangwu; //可以省略到后边主函数当中定义变量int main()
{struct Student s = { "wangwu",20,"yunjisuan",5 }; //变量的创建及初始化struct Student s1 = { "lisi",19,"dashuju",10 };struct Student s1 = { "zhangsan",18,"yuweng",18 };return 0;
}
结构体的自引用:
在结构体当中可以引用自己嘛?答案是可以的,但需要注意以下几点:
1. 结构体自引用时,不可直接使用变量调用本身
2. 自引用时应避免使用typedef函数,否则可能会引起不必要的错误
(注:typedef函数作用是将要更改的函数重新命名,后续使用命名以后的名称起到同样的效果)
举例:
//错误引用方式
struct W
{char q[20];struct W s; //很显然这个引用方式是错误的,如此引用,一层套一层,结构体只会无限变大,这不是我们想要的
};//正确引用方式
struct W
{char q[20];struct W* s;//使用指针引用该结构体的地址
};//typedef错误演示
typedef struct W
{char q[20];N* s; // C语言程序执行是由上往下执行的,此时并没有执行到将结构体命名为N,// 使用程序中的N是什么结构体并不知道,所以错误
}N;
结构体内存对齐:
对齐规则:
- 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处,对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值, VS 中默认的值为 8 Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
- 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍
举例:
struct S1
{char c1; //1与8比,1小,所以此处字节+1int i; //4与8比,4小,但由于1不是4的倍数,所以字节+3来到4字节的位置上,然后+4,来到9字节的位置上char c2; //1与8比,1小,所以此处字节+1,来到9字节的位置上
//最后比较结构体中的最大对齐数 1=1《4,所以最大对齐数为4,9不是4的倍数,所以9+3,来到12字节的位置上
?、所以最后输出的结果为12};int main()
{printf("%d\n", sizeof(struct S1));
}
题解分析:
为什么会有内存对齐注意说法呢,博主找了一些资料供兄弟们参考:
修改默认对齐数:
前边我们了解到在VS当中默认对齐数为8,那我们有没有方法去修改它呢,答案是有,使用#pragme pack(对齐数)即可,下边我们来看一组例子:
#pragma pack(2) //将默认对齐数修改为2
struct S1
{char c1; //1和2,1小,字节+1,到1字节位置处int i; //2和4,2小,由于1不是2的倍数,所以先+1,来到2字节位置处,然后+4,到6字节处char c2; //1和2,1小,6是2的倍数,所以+1,到7,7不是最后结构体中最大对齐数4的倍数,所以+1到8
};
#pragma pack() //重新恢复为默认对齐数8
int main()
{printf("%d\n", sizeof(struct S1));
}//前边我们这段代码的执行结果为12
//现在再执行的话执行结果为8
结构体传参:
前边我们已经了解完了结构体如何创建及使用,那函数调用结构体时,又应该如何传参呢,下边我们一起来看一下:
(注:当变量为结构体变量时,采取(变量名.参数)的形式访问,当变量为指针的时候,采取(指针名->参数)的形式访问)
struct Mode
{char name[10];int age;double ID;
}s;void Test(struct Mode s) //此处传的是整个结构体,传入整个结构体导致调用函数时,内存又得重新开辟一个相同大小的空间,用来专门存储该变量
{s.age = 20;s.name[10] = "zhangsna";s.age = 3.14;
}void Test(struct Mode* s) //传的是结构体的地址,地址在内存当中占4/8个字节,所以相对上边大大减少需要申请的空间
{s->age = 20;s->name[10] = "zhangsna";s->age = 3.14;
}
如此看来,在结构体传参的时候,我们的应该传的是地址,而不是结构体本身
结构体位段的实现:
什么是位段:
举例:
struct Mode
{int a:2; //代表一个int类型中访问两个bit,后续元素访问访问这个bit,直到该类型空间访问完以后,在开辟下一个int型空间int age:3;int ID:4;
}s;
位段的内存分配:
由于位段的特殊性,可能一个字节有多个元素在使用它,使用位段是不能用(&元素名)取地址的
(注:测试环境为VS2022,该平台位段存储顺序是从左往右存储,当剩余存储空间不够存储下一个元素时,舍弃该空间,重新开辟新的空间)
举例:
struct Mode
{char a:2;char age:3;char ID:4;char a2 : 5;
}s;int main()
{s.a = 4;s.age = 6;s.ID = 15;s.a2 = 33;printf("%zd", sizeof(s));
}
题解分析:
总结:由于位段的不确定性,所以一般不支持跨平台使用,因为每个平台对应位段的设定可能是不一样的,用得好,可以节省内存空间,但不清楚规则的情况下使用,可能会达不到你想要的效果,所以关于位段请谨慎使用
(今日分享到此结束,感谢支持,如有高见,欢迎评论区留言,兄弟们一起探讨,Thanks♪(・ω・)ノ)