目录
1. 前言
2. 结构体初识
3. 结构体创建变量
3.1 方法一
3.2 方法二
4. 结构体初始化
5. 结构体自引用
6. 结构体的大小
6.1 结构体对齐规则
6.2 常规结构体
6.3 结构体成员含数组
6.4 结构体嵌套结构体
6.5 为什么存在结构体对齐?
6.6 修改默认对齐数
7. 结构体传参
8. 结构体实现位段
8.1 位段基本讲解
8.2 位段的作用
1. 前言
C语言中有很多种类型,例如:
int ,float ,char ,double, long ,long long ,long double等等,这些叫C语言的内置类型
但是有的时候描述一个人或物只用某一个类型是明显不够的
这时候C语言提供了自定义类型:结构体
2. 结构体初识
我们可以在结构体里面自己使用C语言的内置类型构造我们想要的自定义类型
比如我们需要描述一个学生
struct student
{
char name[20];
int num;
int age;
};
学生有姓名,学号,年龄等等属性,并不是一个int或者char就能表示的,所以我们可以自定义一个类型,里面有姓名,学号,年龄,这些属于结构体的成员,这个struct student 就是我们自己定义的类型
结构体的最后有个分号一定不能漏掉
有了类型,我们就可以跟其它的内置类型一样,创建变量
3. 结构体创建变量
int a = 10;
char c = 'l';
上面我们就用了int 类型和 char类型创建了两个变量a 和 c,然后给它们赋值
结构体也是如此
3.1 方法一
struct student
{
char name[20];
int num;
int age;
};
int main()
{
struct student stu;
return 0;
}
我们用自己创建的类型后面加上变量名字,这样就创建好了一个变量stu
3.2 方法二
struct student
{
char name[20];
int num;
int age;
}stu;
在创建好的结构体后面分号前面直接加变量名字即可
4. 结构体初始化
#include<stdio.h>
#include<string.h>
struct student
{
char name[20];
int num;
int age;
};
int main()
{
struct student stu;
strcpy(stu.name, "xiaoming");
stu.age = 18;
stu.num = 1;
return 0;
}
我们要在结构体变量的后面加个. 才能找到结构体里的成员
然后我们如果给name这个字符数组初始化的话需要使用string.h库里的strcpy来给它赋值
然后下面的age 和 num一样都是用 变量+.操作符找到成员
int 类型就正常赋值即可
然后可以正常打印输出了
printf("名字:%s\n", stu.name);
printf("学号:%d\n", stu.num);
printf("年龄:%d\n", stu.age);
5. 结构体自引用
struct Node
{
int data;
struct Node next;
};
上面的结构体成员里嵌套了一个自身结构体成员next
这个就是结构体嵌套,自引用
但是这样的写法是错误的❌
因为在我们计算结构体大小的时候,还要计算里面的next的大小,但是里面的next又包含了下一个data和下一个next,这样就无限循环下去了导致结构体大小无穷大,是不合理的
正确写法✔:
struct Node
{
int data;
struct Node* next;
};
这也是链表的写法
错误写法❌:
typedef struct
{
int data;
Node* next;
}Node;
因为这个结构体只有创建完成了才会重命名为Node,所以在创建完成前成员里使用Node是不对的
正确写法✔:
typedef struct Node
{
int data;
struct Node* next;
}Node;
6. 结构体的大小
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
猜猜上面代码的结果是什么?
如果你觉得char占1个字节,int占4字节,char占1个字节总共6个字节那么就掉坑了
结构体其实是有它自己的一个对齐规则的
6.1 结构体对齐规则
规则:
1.结构体第一个成员对齐到结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认对齐数 与 该成员变量大小的较小值
3.结构体大小为最大对齐数的整数倍(选所有对齐数中最大的那一个)
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
先来一个个解释一下
6.2 常规结构体
如果这是我们的内存空间,那么我们在存放的时候会先从结构体的第一个成员所占的空间开始占用第一个0
按照上面我们写的结构体,那么第一个char就会占用一个0
然后根据第二个对齐规则,其他成员要对齐到对齐数的整数倍
那么我们要先算对齐数,下一个是int类型,所以算int类型的对齐数
对齐数 = 编译器默认对齐数 与 该成员变量大小的较小值
如果用的是VS,编译器默认对齐数就为8,这是固定的
该成员(int类型)大小为4个字节
所以选择小的作为对齐数所以对齐数就是4
所以我们要占用从4的整数倍开始的4个字节
下一个类型是char,char的字节是1,取最小值作为对齐数当然对齐数就是1了
所以下一个8就是1的倍数,所以直接占用即可
再根据第三个规则
我们现在全部的对齐数最大的为int类型的4,所以整个结构体的大小要为4的整数倍
但是我们看到上图中从0-8有9个字节占用了
所以我们还要再次浪费三个字节的空间满足这个规则
所以最终如图:
所以最后从0-11总共是用了12个字节,那么最终输出的结果当然就是12了
6.3 结构体成员含数组
如果是结构体成员里含数组的情况,其实跟常规的结构体一样
struct S1
{
char c1;
int i;
char c2[5];
};
printf("%d\n", sizeof(struct S1));
这个char数组其实没什么特别的,就把它看成5个char类型的东西即可
然后这5个char类型都是对齐数为1,然后最后再根据最大对齐数int的4对齐结构体总大小即可
如图:
所以是16个字节
下面来看看嵌套:
6.4 结构体嵌套结构体
struct S
{
char c1;
struct S1 s1;
double d;
};
老样子char先占用0偏移量
然后我们上面已经计算过了结构体S1的最大对齐数为4,所以结构体S1对齐数为4
根据第4条对齐规则,可以画出如图:
然后double是8个字节,所以从8的倍数就是16开始到23的这个位置
在最后计算结构体总大小对齐的位置时,嵌套的结构体并不会用总大小参与,而是用它的成员来与其他成员比较,里面明显是double的大小最大,所以按8个字节的整数倍来定义结构体的大小
图中0-23正好是24(8的整数倍),所以最终答案是24
6.5 为什么存在结构体对齐?
我们看到上面的每一个结构体几乎都浪费了很多空间,但这么做其实是有好处的
这是一个以空间换时间的做法
假设一个处理器总是一次性从内存中取8个字节,则拿到的地址对应的都是8的倍数
如果我们能够保证能将double类型的数据都对齐成8的倍数
那么处理器一次扫描就能够将整个double全部取出
而不是扫描两次
这样效率就快了很多
如果我们想要两者兼得,如何做到呢?
目前没什么特别好的方法,但是我们可以尽可能让空间小的成员尽可能的聚在一起
例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
它们两个的成员一模一样,但是
第一个结构体需要12个字节的空间,而第二个只需要8个
6.6 修改默认对齐数
我们其实是可以自己更改默认对齐数的
#pragma pack(1)//默认对齐数改为1
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消修改默认对齐数
int main()
{
printf("%d\n", sizeof(struct S1));
}
结构体成员的对齐数都为1那结构体的总大小就是全部成员的大小相加
所以答案为:
7. 结构体传参
结构体也是和其他内置类型一样作为参数传递给函数的
我们之前有写到这个代码
#include<stdio.h>
#include<string.h>
struct student
{
char name[20];
int num;
int age;
};
int main()
{
struct student stu;
strcpy(stu.name, "xiaoming");
stu.age = 18;
stu.num = 1;
return 0;
}
现在可以用一个Print函数来打印试试看
#include<stdio.h>
#include<string.h>
struct student
{
char name[20];
int num;
int age;
};
void Print(struct student s)
{
printf("%s\n", s.name);
printf("%d\n", s.age);
printf("%d\n", s.num);
}
int main()
{
struct student stu;
strcpy(stu.name, "xiaoming");
stu.age = 18;
stu.num = 1;
Print(stu);
return 0;
}
我们创建了一个 struct student类型的形参s
然后对这个形参进行打印
在传参的时候,我们只需要将我们之前初始化过的 stu传进去即可
这样一样是可以的
8. 结构体实现位段
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};
我们在成员之后加了个:+数字就是位段
什么意思呢?
8.1 位段基本讲解
这些数字表示当前成员所占用的bit的大小
总所周知,一个int类型为4个字节也就是32个bit
所以我们:后面的数字一定不能超过32,因为一个int类型最多32个bit位
8.2 位段的作用
它可以帮我们节省空间,如果我们要开装下成员a和b和c,那么我们只需要开一个int类型即可
32个空间足以装下 2+5+10 = 17的空间
注意事项:
位段尽量不要跨平台使用,因为C语言并未完全的定义位段如何使用,所以不同的编译器都会有它自己的处理方式,所以不要跨平台使用!!!
完