结构体
1.结构体的声明,访问,传参
1.1结构体的基础知识
结构是一些值的合集,这些值成为变量,结构的每个成员可以是不同类型的变量
1.2结构的声明
struct tag
{
member- list; //成员列表
} varivble-list; //变量列表(可有可无)
例:
(1).没有变量列表
//人的结构体,包含姓名,电话,性别,身高
sruct peo
{
char name[20];
char num[12];
char gender[5];
int high;
};
(2).有变量列表
struct peo
{
char name[20];
char num[12];
char gender[5];
int high;
}p1,p2;
其中,p1,p2是使用struct peo结构体类型创建的两个变量,是全局变量,可以不进行初始化
若要p1,p2进行初始化必须在结构体内初始化,代码如下:
struct Peo
{
char name[20];
char num[12];
char gender[5];
int high;
}p1 = {"张三","15336113039","女",167},p2 = {"李四","12345654376","男","181"};
//p1,p2是使用struct peo结构体类型创建的一个变量,并在结构体内进行了初始化
结构体一般在main函数中创建变量,如下:
int main()
{
struct Peo p1;
struct Peo p2 = {"张三","15436783459","男",""178};
//这里对p2进行了初始化,p1未进行初始化
return 0;
}
结构体可以通过typedef进行重定义
代码如下:
typedef struct People
{
char name[20];
char num[12];
char gender[5];
int high;
}People;
这里的最后的People就是对struct People的重定义(与前面的变量列表不是一个东西)
重定义后在main函数中可以直接使用重定义的名字直接创建,如:
int main()
{
People p1 ; //与struct People p1效果一致
return 0;
}
1.3结构体嵌套初始化
struct Peo
{
char name[20];
char num[12];
char gender[5];
int high;
}p = {"zhangsan","12342345453","nv",123};
//p是使用struct peo结构体类型创建的一个变量,并在结构体内进行了初始化
struct St
{
struct Peo p; //结构体的嵌套,在struct St中嵌套了一个struct Peo类型的结构体
int num;
float f; //要注意浮点数在结构体内不能精确保存
};
在main函数中对两个结构体进行初始化,代码如下,注意初始化嵌套的结构体时要使用{ }将嵌套结构体的内容括起来
int main()
{
struct Peo p1 = { "张三","15336113033","男",182}; //主函数中进行初始化
struct St p2 = { {"李四","12342356742","女",167},100,3.4f };
//可以使用printf打印看是否初始化成功
printf("%s %s %s %d %d %f\n", p2.p.name, p2.p.gender, p2.p.num, p2.p.high, p2.num, p2.f);
return 0;
}
1.3结构体传参
函数传参时,参数是需要压栈的
如果传输的一个结构体对象的时候,结构体过大,参数压栈额系统开销会比较大,所以会导致性能的下降
因此结构体传参是要传递地址
typedef struct Peo
{
char name[20];
char num[12];
char gender[5];
int high;
}Peo; //重命名
//正确传参
void print1(Peo* p1) {
printf("%s %s %s %d\n", p1->name,p1->gender,p1->num,p1->high);
}
//错误传参
void print2(Peo p1) { //虽然可以用,但是会浪费空间
printf("%s %s %s %d\n", p1.name, p1.gender, p1.num, p1.high);
}
int main()
{
Peo p1 = {"王五","15335098654","女","170"};
print1(&p1);
print2(p1);
return 0;
}
1.4匿名结构体
在main函数中不能被直接引用,也不能使用typedef重定义
- 匿名结构体一般直接定义在另一个类型内部,不使用结构体名。
- 其成员会被视为包含它的结构体的直接成员.
使用实例如下:
#include <stdio.h>
struct Person {
char name[20];
// 匿名结构体作为成员
struct {
int year;
int month;
int day;
} birthday; // 无需结构体名,但需成员名
};
int main() {
struct Person p = { "Alice",{2000, 1, 15} // 初始化匿名结构体};
// 直接访问匿名结构体的成员
printf("%s 出生于 %d-%d-%d\n",
p.name, p.birthday.year, p.birthday.month, p.birthday.day);
return 0;
}
2.结构体的自引用
在结构体中包含一个类型为该结构体本身的成员
基础形式
struct Node {
int data;
struct Node* next; // 指向同类型结构体的指针
};
关键要点:
- 要使用
struct Node*
而非Node*
(在 C 语言中),因为在结构体定义完成前,Node
这个类型名还未生效。 - 结构体内部存储的是指针,而非结构体自身,因为若直接包含自身,会导致结构体大小变为无限大。
自引用的实现途径
1.显式指针成员
struct TreeNode {
int value;
struct TreeNode* left;
struct TreeNode* right;
};
2.通过 void * 指针(需进行类型转换)
struct GenericNode {
void* data;
struct GenericNode* next;
};
自引用结构体的初始化方法
struct Node a = {10, NULL};
struct Node b = {20, &a}; // b的next指向a
3.结构体内存对齐
一、对齐原理
-
硬件限制
大多数 CPU 更高效地访问对齐的内存(地址是数据大小的整数倍)。例如:- 32 位系统:4 字节对齐(地址为 4 的倍数)
- 64 位系统:8 字节对齐(地址为 8 的倍数)
-
性能考量
未对齐的内存访问可能需要多次读取,降低效率。
二、对齐规则
-
成员对齐值
- 每个成员的起始地址必须是
(成员大小, 编译器指定对齐值)
中较小的一个的整数倍。 - 常见编译器默认对齐值:
- GCC:4 字节(32 位)/ 8 字节(64 位)
- MSVC:8 字节
- 每个成员的起始地址必须是
-
结构体总大小
- 结构体总大小必须是
(所有成员对齐值)
中最大的成员的整数倍。
- 结构体总大小必须是
-
填充字节
编译器在成员之间插入填充字节以满足对齐要求。
三、示例分析
例 1:简单结构体
struct Example1 {
char a; // 1字节,起始地址0
int b; // 4字节,需对齐到4的倍数 → 填充3字节
short c; // 2字节,起始地址8,对齐到2的倍数
};
// 总大小:1(a)+ 3(填充)+ 4(b)+ 2(c)= 10 → 对齐到4的倍数 → 12字节
可以通过调整成员顺序减少内存
struct Example2 {
char a; // 1字节
short c; // 2字节,需对齐到2的倍数 → 填充1字节
int b; // 4字节,起始地址4,对齐到4的倍数
};
// 总大小:1(a)+ 1(填充)+ 2(c)+ 4(b)= 8字节
例 2:带有数组的结构体
struct Example1 {
char a; // 1字节,起始地址0
int arr[2]; // 2个int,每个4字节,需4字节对齐 → 填充3字节
short b; // 2字节,起始地址12,对齐到2的倍数
};
// 总大小:1(a)+ 3(填充)+ 8(arr)+ 2(b)+ 2(填充)= 16字节
例如上述int arr[2]可以看作是两个int类型,对齐数为int字节数4,一共就是4*2 = 8个字节
例 3:嵌套结构体的内存对齐
对齐规则
-
内层结构体对齐
- 内层结构体的首地址必须是其
max(成员对齐值)
的整数倍。 - 内层结构体的大小为其自身对齐值的整数倍。
- 内层结构体的首地址必须是其
-
外层结构体对齐
- 外层结构体的总大小必须是
max(所有成员对齐值, 内层结构体对齐值)
的整数倍。
- 外层结构体的总大小必须是
示例分析
struct Inner {
char c; // 1字节
int i; // 4字节 → 填充3字节
};
// Inner大小:1 + 3(填充) + 4 = 8字节,对齐值为4
struct Outer {
short s; // 2字节
struct Inner inner; // 8字节,需4字节对齐 → 填充2字节
char d; // 1字节 → 填充3字节(满足Outer总大小为4的倍数)
};
// Outer总大小:2(s) + 2(填充) + 8(inner) + 1(d) + 3(填充) = 16字节
四、修改默认对齐
使用#pragma() 这个预处理指令可以修改默认对齐值,使用时需要在修改的结构体前后都写,后面可以省略括号内的参数
如#pragma(2=4): 使用后默认对齐数就是4
#pragma pack(4)
struct S {
int i;
double d;
//double对齐数是8,但是修改后的默认对齐值是4,小于8,因此只需要是4的倍数即可
};
#pragma pack()
int main()
{
printf("%zu", sizeof(struct S)); //本应是4(i) + 4(填充) + 8(d) = 16,
//修改后为4(i) + 8(d) = 12
return 0;
}