目录
定义一:结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
定义二:其他成员变量要对齐到某个数字(对⻬数)的整数倍的地址处。
前言
大家好,这里是彩妙呀,今天给大家带来的是 --> 自定义类型的讲解。
我们在学习C语言的过程中,或多或少不可避免地会接触到各种数据类型,比如我们熟知的基本数据类型int
、char
等。然而,当需要处理更复杂的数据或者更大规模的项目时,这些基本数据类型就显得力不从心。幸运的是,C语言为我们提供了几种强大的自定义类型工具:结构体(Structures)、联合体(Unions)和枚举体(Enumerations)。这里,彩妙 将详细介绍这 三种 自定义类型之一:结构体的定义、使用方式及它们的应用场景。
废话不多说,让我们开始吧!
结构体(Structures)
定义:结构体 是一种自定义的数据类型,可以用来存储不同类型的数据并组合成一个整体。
简单的说,结构体就是把 一个 或 多个 的 数据类型 组合在一起的一种 数据类型,相当于 是 ⼀些值 的集合,这些值称为成员变量。结构 的 每个成员 可以是 不同类型的变量,包括基本类型(如int、float)、指针类型和其他结构体类型等。
那我们怎么去定义一个结构体呢?
结构体的定义
struct name // 结构体类型的名字
{
value-list; // 结构体内部成员
}name-list; // 该结构体的名字
这里用学生的例子来给大家在进一步说明:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
在这里,struct Stu 的作用与咱们熟知的基本类型(int , char)作用一样,起到声明作用。假如,我要去声明一个整形 a ,就要写:int a;同样的,我要去声明一个我定义好的结构体 a,就要这么写:struct Stu a。
对于结构体内部成员 ,我们可以用 “ . ” 或者 " -> " 来去引用相对应的成员。例如,我已经定义了一个结构体成员 a,要去修改恰其中 name[20] 这个成员的内容,就可以用 a.name = "张三" 或者 a-> name = "张三" 来进行修改
在这里,你也会发现,没有 name-list 这部分,原因是 name-list 这部分相当于在你定义结构体的时候就声明了一些结构体。
知道了各个部分的作用,那我们就要去使用它。
结构体变量的创建和初始化
接上上面学生的例子来说,我们如何去创建与初始化一个结构体呢?
先上代码:
#include <stdio.h> struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; int main() { //按照结构体成员的顺序初始化 struct Stu s = { "张三", 20, "男", "20240604001" }; 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 = "李四", .id = "20240604002", .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 Stu s = { "张三", 20, "男", "20240604001" };不难发现,初始化所对应的 各个数据都 一 一对应结构体中成员的类型。
name:张三
age:20
sex:男
id:20240604001
按照指定的顺序初始化:
代码区:struct Stu s2 = { .age = 18, .name = "李四", .id = "20240604002", .sex = "⼥"};也不难发现,这里的初始化是按照你所给的类型名一一去修改的。
那为什么会有这种不同的初始方法呢?这就和接下来的结构体的一个规则有关啦。
结构体的内存对齐
在初始化中,难免会有疑问,为什么计算机去初始化数据时要按着这些顺序来存储?在指定的顺序初始化中,为什么可以去精准找到每个成员所储存的地址呢?
这些问题的底层逻辑都离不开结构体自身的规则:内存对齐规则。
对齐规则
定义:
1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处2. 其他成员变量要对齐到某个数字(对⻬数)的整数倍的地址处。对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
这些定义看着就头痛,接下来彩妙带着大家逐步掌握结构体的储存逻辑吧。
定义一:结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
定义二:其他成员变量要对齐到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
对于大多数数据类型,例如int
、float
、char
等,它们的大小通常是4个字节(32位系统)或8个字节(64位系统)。因此,它们的对齐数也是4或8。
举个例子:
struct Str{
int a;
float b;
char c;
};
在32位系统上,int
的大小是4,所以a
的对齐数为4。float
的大小也是4,所以b
的对齐数也是4。char
只有1个字节,所以c
的对齐数为1。因此,结构体中的成员变量都应该对齐到4的整数倍的地址处。
结构体的大小计算如下:
a
的偏移量为0, 大小为4。b的偏移量为4, 大小为4。
c
的偏移量为8, 大小为1。
![](https://i-blog.csdnimg.cn/direct/78cc5b0160d145f5b7d9b5a0129e484d.png)
定义三:结构体总大小为最大对齐数的整数倍。
在C语言中,结构体的总大小是由其成员的大小和对齐方式决定的。每个成员的对齐方式取决于编译器和平台,但通常会遵循一定的规则,比如成员的对齐数通常是2的幂次。结构体的总大小是其成员大小的总和,但还需要满足一个条件:结构体的总大小必须是其最大成员对齐数的整数倍。
举个例子:
#include <stdio.h>
struct Str{
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
int main() {
printf("结构体的字节数: %zu\n", sizeof(struct Str));
return 0;
}
在这个例子中,char a
占用1字节,int b
通常占用4字节,short c
通常占用2字节。如果按照成员的大小累加,结构体的大小应该是1 + 4 + 2 = 7字节。
但是,由于结构体的总大小需要是其最大成员对齐数(这里是 int
类型的对齐数,通常是4字节)的整数倍,编译器会在结构体的末尾添加填充字节,使得结构体的总大小为 12 字节。
黄色区域是被占用的字节,灰色区域是为了使内存对齐所弃用的字节
运行结果:
结构体的字节数: 12
如何去修改结构体默认对齐数
为了确保成员变量都对齐到4的整数倍的地址处,可以使用#pragma pack
指令来设置对齐数。例如,可以使用#pragma pack(4)
来设置对齐数为4,即:
#pragma pack(4)
struct example {
int a;
float b;
char c;
};
#pragma pack()
这样,编译器会按照对齐数为4的要求分配内存,保证结构体中的成员变量都对齐到4的整数倍的地址处。
注意:VS 中默认的对齐数的值为 8,而Linux中 gcc 没有默认对⻬数,其对⻬数就是成员⾃⾝的⼤⼩
为什么会存在内存对齐呢
平台原因 (移植原因):
性能原因:
有兴趣可以去查阅《深入理解计算机系统》,但总的来说,结构体的内存对齐 是拿空间来换取时间的做法。
结语
这次为大家介绍了结构体,下次就为大家介绍一下与结构体同兄弟的两个:联合和枚举。我们下次再见