什么是结构体?
在 C 语言中,结构体 是一种用户自定义的数据类型,它允许你将不同类型的变量组合成一个单一的、有意义的整体。

(图片来源网络,侵删)
打个比方:
- 一个
int类型只能存储一个整数。 - 一个
float类型只能存储一个浮点数。 - 但一个“学生”的信息,包含了姓名(字符串)、年龄(整数)、成绩(浮点数)等多个不同类型的数据。
结构体就相当于一个“模板”,我们可以用它来创建“学生”这样的数据类型,把学生的所有信息都打包在一起。
为什么需要结构体?
- 数据组织:将相关联的数据项组合在一起,使代码更有条理,更易于理解。
- 数据传递:当需要传递一个复杂对象的所有数据时,传递一个结构体变量比传递多个独立的参数要方便得多。
- 创建复杂数据结构:结构体是构建链表、树、图等复杂数据结构的基础。
如何定义和声明结构体?
1 定义结构体类型
使用 struct 关键字来定义一个结构体,定义只是创建了一个“蓝图”,并不会分配内存。

(图片来源网络,侵删)
struct Student {
char name[50]; // 成员:姓名
int age; // 成员:年龄
float score; // 成员:成绩
};
struct Student是这个新类型的名称。name,age,score是结构体的成员或字段。- 注意:定义的末尾需要一个分号 ,这是一个非常常见的初学者错误点。
2 声明结构体变量
定义好类型后,我们就可以像使用 int, char 一样来声明变量了。
先定义类型,再声明变量
struct Student {
char name[50];
int age;
float score;
};
// 声明两个 Student 类型的变量
struct Student student1;
struct Student student2;
在定义类型的同时声明变量
struct Student {
char name[50];
int age;
float score;
} student1, student2; // 直接在这里声明变量
使用 typedef 创建别名(强烈推荐)

(图片来源网络,侵删)
typedef 可以为一个已有的类型(包括我们自定义的结构体)创建一个新的名称,这能让代码更简洁、更易读。
// 定义结构体 Student 并为其创建别名 Stu
typedef struct {
char name[50];
int age;
float score;
} Stu;
// 现在可以直接使用 Stu 来声明变量,而无需 struct Student
Stu student1;
Stu student2;
最佳实践:在 C 语言中,使用
typedef为结构体创建别名是一种非常普遍且推荐的做法。
如何访问和初始化结构体成员?
使用成员访问运算符(也叫“点运算符”) 来访问结构体变量的成员。
1 初始化
#include <stdio.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float score;
} Stu;
int main() {
// 声明并初始化
Stu student1 = {"张三", 18, 95.5f};
// 先声明,后逐个赋值
Stu student2;
strcpy(student2.name, "李四"); // 字符串不能直接用 = 赋值,需要 strcpy
student2.age = 19;
student2.score = 88.0f;
// 访问并打印成员
printf("学生1: 姓名=%s, 年龄=%d, 成绩=%.1f\n", student1.name, student1.age, student1.score);
printf("学生2: 姓名=%s, 年龄=%d, 成绩=%.1f\n", student2.name, student2.age, student2.score);
return 0;
}
2 访问和修改成员
// 修改 student1 的成绩
student1.score = 98.0f;
// 访问成员
printf("%s 的最新成绩是: %f\n", student1.name, student1.score);
结构体的其他重要特性
1 结构体数组
可以创建一个结构体数组,来存储多个相同类型的结构体对象。
Stu class[5]; // 创建一个可以存放5个学生的数组
// 初始化数组
Stu class[5] = {
{"张三", 18, 95.5},
{"李四", 19, 88.0},
{"王五", 18, 92.5},
{"赵六", 20, 76.5},
{"钱七", 19, 85.0}
};
// 访问数组中的元素
printf("第二个学生是: %s\n", class[1].name);
2 结构体指针
可以声明一个指向结构体的指针。
Stu student = {"王五", 18, 92.5f};
Stu *ptr = &student; // ptr 指向 student 变量的地址
// 通过指针访问成员
// 方式1: 使用解引用运算符 * 和点运算符 .
printf("姓名: %s\n", (*ptr).name);
// 方式2: 使用结构体指针成员访问运算符 ->
// 这是更常用、更简洁的方式
printf("年龄: %d\n", ptr->age);
printf("成绩: %.1f\n", ptr->score);
-> 和 的区别:
- 用于结构体变量。
->:用于结构体指针。
3 结构体作为函数参数
结构体可以作为函数的参数传递,主要有两种方式:
- 传值:将整个结构体的副本传递给函数,这会消耗较多内存和CPU时间,特别是当结构体很大时。
- 传指针:只传递结构体的地址给函数,这非常高效,函数内部可以通过指针修改原始结构体的内容。
示例:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
// 传值:函数内部修改不会影响原始变量
void printPointByValue(Point p) {
p.x = 100; // 修改副本
printf("函数内部 (传值): x = %d, y = %d\n", p.x, p.y);
}
// 传指针:函数内部修改会影响原始变量
void printPointByPointer(Point *p) {
p->x = 200; // 修改原始变量
printf("函数内部 (传指针): x = %d, y = %d\n", p->x, p->y);
}
int main() {
Point myPoint = {10, 20};
printf("调用前: x = %d, y = %d\n", myPoint.x, myPoint.y);
printPointByValue(myPoint);
printf("传值调用后: x = %d, y = %d\n", myPoint.x, myPoint.y); // myPoint.x 仍然是 10
printPointByPointer(&myPoint);
printf("传指针调用后: x = %d, y = %d\n", myPoint.x, myPoint.y); // myPoint.x 变成了 200
return 0;
}
为了避免不必要的拷贝开销,并且允许函数修改原始数据,通常推荐使用指针来传递结构体。
结构体的内存对齐
这是一个高级但非常重要的概念,为了提高内存访问效率,编译器会在结构体成员之间插入一些“填充字节”(Padding),使得每个成员的内存地址都是其自身大小的整数倍。
示例:
#include <stdio.h>
typedef struct {
char c; // 1 字节
int i; // 4 字节
short s; // 2 字节
} TestStruct;
int main() {
printf("sizeof(char) = %zu\n", sizeof(char)); // 1
printf("sizeof(int) = %zu\n", sizeof(int)); // 4
printf("sizeof(short) = %zu\n", sizeof(short)); // 2
printf("-----------------\n");
printf("sizeof(TestStruct) = %zu\n", sizeof(TestStruct)); // 输出可能是 12,而不是 1+4+2=7
return 0;
}
为什么是 12? 内存布局可能是这样的:
[ c ] [ Pad3 ] [ i ] [ s ] [ Pad2 ]
1 3 4 2 2
char c占 1 字节。- 为了让
int i的地址是 4 的倍数,需要在c后面填充 3 个字节。 int i占 4 字节。short s占 2 字节,其地址已经是 2 的倍数(8),所以不需要填充。- 结构体的总大小(12)必须是其中最大成员大小(4)的倍数,因为 7 不是 4 的倍数,所以需要在最后再填充 2 个字节。
如何控制内存对齐?
可以使用 #pragma pack 指令来告诉编译器使用特定的对齐方式,但这通常只在需要与硬件或其他语言交互时才使用。
| 特性 | 描述 | 示例 |
|---|---|---|
| 定义 | struct 关键字创建自定义数据类型。 |
struct Point { int x; int y; }; |
| 声明 | 创建结构体类型的变量。 | struct Point p1; 或 typedef struct { ... } Point; Point p1; |
| 成员访问 | 使用 运算符访问变量成员,-> 访问指针成员。 |
p1.x = 10; Point *ptr = &p1; ptr->y = 20; |
| 初始化 | 使用花括号 在声明时初始化。 | Point p = {10, 20}; |
| 数组 | 可以创建结构体数组。 | Point points[10]; |
| 函数参数 | 可以传值(副本)或传指针(地址),推荐传指针以提高效率。 | void func(Point p); vs void func(Point *p); |
| 内存对齐 | 编译器为优化访问速度而插入填充字节,导致 sizeof 结果可能不等于成员大小之和。 |
sizeof(MyStruct) |
掌握结构体是学习 C 语言迈向高级编程的关键一步,它是理解面向对象编程(C++中的 class 就是在 struct 基础上发展而来的)和复杂数据结构的基础。
