前言
还记得我在总结操作符时,涉及到了结构体,在C语言中类型分为两大类一个是内置类型,一个是自定义类型,常见的内置类型我们不说,我们今天来好好看一看为自定义类型之一的结构体吧
个人主页:小张同学zkf
若有问题 评论区见
感兴趣就关注一下吧
目录
1.什么是结构体
结构体由一系列成员(member)组成,每个成员可以是不同的数据类型。这些成员通常通过结构体的名称和点运算符来访问,结构体可以被声明为变量、指针或数组,用于存储和操作包含多种数据类型的复杂数据。在编程中,结构体常用于封装相关属性,以便于管理和使用。
2.结构体类型的声明
结构体声明的格式
struct tag{member- list ;}variable- list ;
来我们来举一个例子,假如我们用结构体来描述一个学生,用代码就可以表示
struct Stu{char name[ 20 ]; // 名字int age; // 年龄char sex[ 5 ]; // 性别char id[ 20 ]; // 学号}; // 分号不能丢
接着我们将它初始化
# include <stdio.h>struct Stu{char name[ 20 ]; // 名字int age; // 年龄char sex[ 5 ]; // 性别char id[ 20 ]; // 学号};int main (){// 按照结构体成员的顺序初始化struct Stu s = { " 张三 " , 20 , " 男 " , "20230818001" };printf ( "name: %s\n" , s.name);printf ( "age : %d\n" , s.age);printf ( "sex : %s\n" , s.sex);printf ( "id : %s\n" , s.id);// 按照指定的顺序初始化struct Stu s2 = { .age = 18 , .name = "lisi" , .id = "20230818002" , .sex =" ⼥ " };printf ( "name: %s\n" , s2.name);printf ( "age : %d\n" , s2.age);printf ( "sex : %s\n" , s2.sex);printf ( "id : %s\n" , s2.id);return 0 ;}
以上就是正常的结构体的创建
但有一些特殊情况,让我们来看一下
// 匿名结构体类型struct{int a;char b;float c;}x;struct{int a;char b;float c;}a[ 20 ], *p;
这种就是匿名结构体类型,你会发现这个结构体类型没有名字,那么可以p = &x吗
编译器会把这两个没有名字的结构体当做两个不同的类型,所以这是非法的
3.结构的自引用
比如,定义一个链表(之后的数据结构的博客会总结,这里先知道一下)
struct Node{int data;struct Node next ;};
struct Node{int data;struct Node * next ;};
typedef struct{int data;Node* next;}Node;
typedef struct Node{int data;struct Node * next ;}Node;
4. 结构体内存对齐
我们了解了结构体声明,但是我们如何计算一个结构体的大小那
其实我们需要了解一个规则——对齐规则
1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处2. 其他成员变量要对齐到某个数字( 对齐数 )的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。- VS 中默认的值为 8- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
光说结论肯定不好理解,我们来看几道题
struct S1{char c1;int i;char c2;};printf ( "%d\n" , sizeof ( struct S1));
来看一下,这道题char c1变量大小是1吧,它是第一个成员,对齐到偏移量为0的地址处,接下来的成员int i 大小为4,与vs默认的8比较是不是比8小,那这个成员的最大对齐数就是4,对齐到最大对齐数的整数倍处,那就是从偏移量4开始对齐,对齐完4个字节,接下来再来看最后一个成员名,是char类型大小为1与默认的8比较得最大对齐数为1,那就是1的倍数,那就紧接着int对齐完之后再来一个空间就行了,成员都对齐完了,那我们就开始算结构体的总大小,那就是所有最大对齐数中最大的那个的整数倍,第一成员是1,第二个是4,第三个是1,里面最大的是4,所以是4的整数倍,此时空间大小为为9不是4的整数倍,那就是空几个空间到12就是4的整数倍,所以结构体大小为12
再来一道
// 练习 2struct S2{char c1;char c2;int i;};printf ( "%d\n" , sizeof ( struct S2));
这个和上面就是成员位置变了,c1依旧是在偏移量为0的首地址处,c2最大对齐数是1,那就在偏移量为1的地址处,i最大对齐数是4,那就在偏移量为4的倍数的地址处,所以在偏移量为4的地址处,往后四个字节,到偏移量7结束,此时空间占了8个,正好是1,1,4中最大为4的倍数,那此时结构体总大小就会变化 ,变成了8
我们继续看一个
struct S3{double d;char c;int i;};printf ( "%d\n" , sizeof ( struct S3));
这个第一个成员类型为double,大小为8个字节,与默认的8一样,所以最大对齐数为8,为首成员,所以在偏移量为0的地址处向后申请8个字节,到偏移量为7的空间,再看第二个,char类型大小为1个字节,与默认的8比较,1小所以最大对齐数为1,1的整数倍那直接在偏移量为8中存放就行了, 最后一个int,4比8小,所以最大对齐数为4,在偏移量为四的整数倍处存储,相当于跳到偏移量为12中存储,向后访问四个字节空间。
此时总大小为16,16正好为最大对齐数中最大的值为8的整数倍,所以此时大小就是结构体的总大小为16
如果结构体里套一个结构体,那结果会是什么那
我们来看一下
struct S4{char c1;struct S3 s3 ;double d;};printf ( "%d\n" , sizeof ( struct S4));
还是按上面的思路分析,首成员大小为1,与默认对齐数8比交,1是最大对齐数,因为是首成员,所以从偏移量为0开始,占一个字节,接着这个结构体s3是上面那个练习题的s3,此时我们要知道结构体的对齐数为里面成员的最大对齐数最大的那个数,上面s3我们在求时最大对齐数中最大值为8,所以被用来当作这个结构体s3的对齐数,与默认的8比较相等,所以要在偏移量为8的倍数处存储,这个就在偏移量为8处向后16个字节,到偏移量为23处,第三个成员double为8个字节与默认8比较相等,就在8的倍数处存储,此时就在偏移量24处存储,此时三个成员最大对齐数中的最大值为8,此刻总大小为32正好是8的倍数,所以这个结构提的总大小为32.
那么为什么结构体要存在内存对齐
分为两个原因
5.修改默认对齐数
# include <stdio.h># pragma pack(1) // 设置默认对⻬数为 1struct S{char c1;int i;char c2;};# pragma pack() // 取消设置的对⻬数,还原为默认int main (){// 输出的结果是什么?printf ( "%d\n" , sizeof ( struct S));return 0 ;}
此时利用 #pragma把默认对齐数8改为默认对齐数1,那第一个成员的最大对齐数为1,第二个成员的最大对齐数为1,第三个成员的最大对齐数为1,所以只需要紧挨着存放就行了,结构体大小为6
6.结构体传参
我们来看一个代码仔细看一下结构体如何传参
struct S{int data[ 1000 ];int num;};struct S s = {{ 1 , 2 , 3 , 4 }, 1000 };// 结构体传参void print1 ( struct S s){printf ( "%d\n" , s.num);}// 结构体地址传参void print2 ( struct S* ps){printf ( "%d\n" , ps->num);}int main (){print1(s); // 传结构体print2(&s); // 传地址return 0 ;}
附加在字符串那个博客说过结构体指针变量调用成员的话用‘->’操作符,结构体变量调用成员的话用‘.’操作符
7.结构体实现位段
7.1什么是位段
记住这个是位段,不是我们游戏中的段位!!!哈哈哈开玩笑
回到正题,位段其实是一种特殊的结构体
1. 位段的成员必须是 int 、 unsigned int 或 signed int ,在C99中位段成员的类型也可以选择其他类型。2. 位段的成员名后边有一个冒号和一个数字。后面那个数字就是给你这个数所占的比特位
举一个例子
struct A{int _a: 2 ;int _b: 5 ;int _c: 10 ;int _d: 30 ;};
7.2位段的内存分配
// ⼀个例⼦struct S{char a: 3 ;char b: 4 ;char c: 5 ;char d: 4 ;};struct S s = { 0 };s.a = 10 ;s.b = 12 ;s.c = 3 ;s.d = 4 ;// 空间是如何开辟的?
有图可知,在vs2013中a和b在一个内存空间中当剩余的比特位不够下一个成员进入时,再给它开辟一个空间,让成员在新的空间存放,依次按照这样的方式存放
7.3位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用剩余的位段,这是不确定的。
位段使用的注意事项:
位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。
结束语
结构体知识就总结到这里了,下篇博客我们来看看同为自定义类型家族的联合体和枚举
OK,感谢观看!!!