struct 是 C 语言中一种非常重要的复合数据类型,它允许你将不同类型的数据项组合成一个单一的类型,你可以把它想象成一个“容器”或者一个“蓝图”,用来描述一个更复杂的数据结构。

为什么需要 struct?
想象一下你要描述一个“学生”的信息,一个学生有:
- 姓名(字符串)
- 学号(整数)
- 年龄(整数)
- 成绩(浮点数)
如果用 C 语言的基本类型,你可能需要定义多个独立的变量:
char name[50]; int id; int age; float score;
这样做有几个问题:
- 数据不相关:这些变量在逻辑上是相关的,但在代码中是分散的。
- 参数传递麻烦:如果你想把一个学生的信息传递给一个函数,你需要传递所有这些变量,非常繁琐。
- 难以管理:当学生数量增多时,管理成百上千个独立的变量会是一场噩梦。
struct 就是为了解决这个问题而生的,它能把所有这些相关的数据项打包成一个名为 Student 的单一类型。

如何定义和使用 struct
1 定义结构体类型
定义一个结构体类型,需要使用 struct 关键字。
语法:
struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// ...
数据类型 成员N;
};
示例:定义一个 Student 结构体
// 定义一个名为 Student 的结构体类型
struct Student {
char name[50];
int id;
int age;
float score;
};
注意:这只是一个类型定义,像 int 一样,它本身不占用内存,它只是创建了一个“模板”或“蓝图”。

2 声明结构体变量
定义好类型后,你就可以像使用 int, char 一样来声明变量了。
先定义类型,再声明变量
struct Student { /* ... 成员 ... */ }; // 类型定义
// 声明两个 Student 类型的变量
struct Student student1;
struct Student student2;
在定义类型的同时声明变量
// 定义 Student 类型,并同时声明 s1 和 s2 两个变量
struct Student {
char name[50];
int id;
int age;
float score;
} s1, s2;
使用 typedef 创建别名(推荐)
为了在声明变量时省去 struct 关键字,通常会使用 typedef,这是一种非常普遍和推荐的用法。
// 定义 Student 结构体类型,并给它起一个别名叫 Student
typedef struct {
char name[50];
int id;
int age;
float score;
} Student;
// 现在可以直接用 Student 来声明变量,无需 struct
Student student1;
Student student2;
如何访问结构体成员?
要访问结构体变量中的成员,使用成员访问运算符,也就是点号 。
语法:
结构体变量名.成员名
示例:
#include <stdio.h>
#include <string.h>
// 使用 typedef 定义 Student
typedef struct {
char name[50];
int id;
int age;
float score;
} Student;
int main() {
// 声明并初始化一个 Student 变量
Student student1;
strcpy(student1.name, "张三"); // 字符串不能直接用 = 赋值,需要 strcpy
student1.id = 1001;
student1.age = 20;
student1.score = 95.5;
// 声明另一个变量并逐个赋值
Student student2;
student2.age = 21;
student2.score = 88.0;
// ... 其他成员赋值
// 访问并打印成员
printf("学生姓名: %s\n", student1.name);
printf("学生学号: %d\n", student1.id);
printf("学生年龄: %d\n", student1.age);
printf("学生成绩: %.2f\n", student1.score);
return 0;
}
结构体的初始化
你可以在声明结构体变量的同时,使用花括号 来初始化它的成员。
示例:
Student student1 = {"李四", 1002, 19, 92.5};
// 如果只初始化部分成员,未初始化的成员会被自动设置为0(对于数值类型)或'\0'(对于字符)
Student student2 = {"王五", 1003}; // age 和 score 会被自动初始化为 0
结构体数组
你可以创建一个结构体数组,来存储多个相同类型的结构体对象。
示例:
Student class[30]; // 定义一个可以容纳30个学生的数组 // 访问数组中的元素 class[0].id = 1001; strcpy(class[0].name, "张三"); class[1].id = 1002; strcpy(class[1].name, "李四");
结构体指针
你也可以声明一个指向结构体的指针。
关键点:
- 使用
->(箭头运算符)来通过指针访问成员。 ->是(*指针名).成员名的简写形式。
示例:
Student student = {"赵六", 1004, 22, 99.0};
Student *ptr; // 声明一个结构体指针
ptr = &student; // 让指针指向 student 变量的地址
// 通过指针访问成员
printf("姓名: %s\n", ptr->name); // 使用 -> 运算符
printf("学号: %d\n", ptr->id);
// 也可以使用解引用的方式
printf("成绩: %f\n", (*ptr).score);
结构体作为函数参数
结构体可以作为函数的参数传递,主要有两种方式:
a) 传值(Pass-by-Value)
将结构体的副本传递给函数,函数内部对副本的修改不会影响原始变量,这种方式简单,但当结构体很大时,会消耗较多内存和时间进行拷贝。
void printStudent(Student s) {
printf("打印学生信息: %s, %d\n", s.name, s.id);
}
int main() {
Student student = {"钱七", 1005, 23, 85.5};
printStudent(student); // 传递副本
return 0;
}
b) 传指针(Pass-by-Pointer)
传递结构体的地址,函数通过指针直接操作原始变量,效率更高,并且可以修改原始数据。
void updateScore(Student *s, float newScore) {
s->score = newScore; // 通过指针修改原始数据
}
int main() {
Student student = {"孙八", 1006, 24, 75.0};
printf("修改前成绩: %.2f\n", student.score);
updateScore(&student, 80.5); // 传递地址
printf("修改后成绩: %.2f\n", student.score);
return 0;
}
对于较大的结构体,推荐使用指针传递,以提高效率。
结构体的大小与内存对齐
sizeof 运算符可以获取结构体的大小。
printf("Student 结构体的大小是: %zu 字节\n", sizeof(Student));
你会发现,sizeof(Student) 的大小通常大于其所有成员大小之和(sizeof(name) + sizeof(id) + ...),这是因为编译器为了提高内存访问效率,会进行内存对齐。
- 什么是内存对齐? 编译器会在结构体成员之间填充一些字节,使得每个成员都从某个特定地址(通常是它自身大小的整数倍)开始存放。
- 为什么要对齐? 现代CPU在访问内存时,如果地址是对齐的,速度会更快,unaligned access 可能会导致性能下降甚至程序崩溃。
- 如何优化? 将小的数据类型放在一起,大的数据类型放在一起,可以减少填充字节,从而减小结构体总大小。
示例:
struct BadExample {
char c; // 1 byte
int i; // 4 bytes (在 c 后面填充了 3 bytes)
double d; // 8 bytes
};
struct GoodExample {
double d; // 8 bytes
int i; // 4 bytes (在 d 后面填充了 4 bytes)
char c; // 1 byte (在 i 后面填充了 3 bytes)
};
// BadExample 的大小可能是 1 + 3 + 4 + 4 + 8 = 20 字节 (取决于编译器)
// GoodExample 的大小可能是 8 + 4 + 4 + 1 + 3 = 20 字节 (取决于编译器)
// 这个例子对齐效果不明显,但原则是:把类型相近的放在一起。
结构体与 typedef 的常见写法
初学者可能会对 typedef 和 struct 的组合感到困惑,这里列出最常见的几种写法:
最清晰、推荐
先定义结构体,再用 typedef 起别名。
struct Point {
int x;
int y;
};
typedef struct Point Point; // 给 struct Point 起一个别名 Point
或者更简洁的写法(省略 Point):
struct Point {
int x;
int y;
};
typedef struct Point Point; // 等同于 typedef struct Point Point;
优点:代码清晰,可读性高,struct Point 和 Point 都可以使用。
匿名结构体 + typedef (最常用)
直接在 typedef 中定义结构体。
typedef struct {
int x;
int y;
} Point;
优点:简洁,一行代码搞定,这是在C语言项目中最常见的写法,缺点是,如果想在别处使用 struct Point 这个完整的类型名(例如在函数参数中明确表示是结构体类型),则不行。
不推荐(易混淆)
typedef struct Point {
int x;
int y;
} Point;
这种写法功能上和写法二一样,但 Point 这个名字在 struct 内部和外部都存在,可能会让一些初学者混淆,写法二(匿名结构体)更清晰,表明 Point 这个名字是 typedef 给整个结构体的别名,而不是结构体内部的某个东西。
| 特性 | 描述 |
|---|---|
| 定义 | struct 是一种复合数据类型,用于组合不同类型的数据。 |
| 声明 | struct Student s1; 或使用 typedef 后 Student s1;。 |
| 访问 | 使用点号 访问变量成员,使用箭头 -> 访问指针成员。 |
| 初始化 | 在声明时使用 初始化。 |
| 数组 | 可以创建结构体数组 struct Student class[100];。 |
| 指针 | 指向结构体的指针用于高效地传递和操作结构体数据。 |
| 参数 | 可以传值(副本)或传指针(地址),后者更高效。 |
| 内存 | 存在内存对齐,sizeof 结果可能大于成员大小之和。 |
typedef |
强烈推荐使用 typedef 来简化结构体类型的声明。 |
struct 是 C 语言构建复杂数据结构(如链表、树、图等)的基础,也是面向对象编程思想在 C 语言中的雏形(C++ 中的 class 就是在 struct 的基础上扩展而来的),掌握 struct 是从 C 语言新手进阶到熟练的关键一步。
