struct 是 C 语言中一种非常重要的复合数据类型,你可以把它想象成一个自定义的“蓝图”或“模板”,它允许你将不同类型的数据项组合成一个单一的类型。

为什么需要 struct?
想象一下你要描述一个“学生”的信息,一个学生有:
- 姓名(字符串)
- 学号(整数)
- 年龄(整数)
- 平均成绩(浮点数)
在 C 语言中,如果使用独立的变量来存储这些信息,会非常混乱且难以管理:
char name[50]; int id; int age; float score;
当你需要传递一个学生的所有信息时,你必须一个一个地传递这些变量,而 struct 可以完美地解决这个问题。
struct 的基本定义和使用
1 定义结构体类型
使用 struct 关键字来定义一个新的数据类型,语法如下:

struct 结构体名称 {
数据类型 成员1;
数据类型 成员2;
// ...
};
示例:定义一个 Student 结构体
// 定义一个名为 Student 的结构体类型
struct Student {
char name[50];
int id;
int age;
float score;
};
重要提示:
- 这只是定义了一个类型,就像
int,float一样,它还没有分配内存空间,它只是一个模板。 - 定义结束时,需要一个分号 ,这是初学者最容易忘记的错误之一。
2 声明结构体变量
定义好结构体类型后,我们就可以像使用基本类型一样声明变量了。
直接声明

struct Student student1; // 声明一个名为 student1 的 Student 类型的变量
编译器会为 student1 分配一块连续的内存空间,足以容纳 name, id, age, 和 score。
在定义时声明
struct Student {
char name[50];
int id;
int age;
float score;
} student1, student2; // 同时声明了两个变量 student1 和 student2
使用 typedef(推荐方式)
为了简化代码,通常使用 typedef 为结构体类型创建一个简短的别名。
typedef struct {
char name[50];
int id;
int age;
float score;
} Student; // Student 现在是一个类型别名,可以直接使用
// 现在声明变量就非常简洁了
Student student1;
Student student2;
这是 C 语言中非常普遍和推荐的做法。
3 访问结构体成员
使用成员访问运算符( 点运算符)来访问和修改结构体内部的数据。
#include <stdio.h>
#include <string.h>
// 使用 typedef 定义 Student 类型
typedef struct {
char name[50];
int id;
int age;
float score;
} Student;
int main() {
// 声明并初始化一个结构体变量
Student student1;
student1.id = 1001;
student1.age = 20;
student1.score = 95.5;
strcpy(student1.name, "张三"); // 使用 strcpy 复制字符串到字符数组
// 声明并初始化另一个变量
Student student2 = {"李四", 1002, 21, 88.0};
// 打印结构体成员信息
printf("学生1的信息:\n");
printf(" 姓名: %s\n", student1.name);
printf(" 学号: %d\n", student1.id);
printf(" 年龄: %d\n", student1.age);
printf(" 成绩: %.1f\n", student1.score);
printf("\n学生2的信息:\n");
printf(" 姓名: %s\n", student2.name);
printf(" 学号: %d\n", student2.id);
printf(" 年龄: %d\n", student2.age);
printf(" 成绩: %.1f\n", student2.score);
return 0;
}
结构体的初始化
可以在声明结构体变量的同时,使用花括号 进行初始化,初始化的顺序必须与结构体定义中成员的顺序一致。
Student student = {"王五", 1003, 22, 91.2};
如果只想初始化部分成员,C99 标准支持指定初始化器:
Student student = {.id = 1004, .score = 76.5}; // 其他成员会被自动初始化为 0 或空字符串
结构体数组
你可以创建一个结构体数组,来存储多个相同类型的结构体。
#include <stdio.h>
typedef struct {
char name[20];
int id;
} Person;
int main() {
// 定义一个 Person 结构体数组,包含 3 个元素
Person people[3] = {
{"Alice", 1},
{"Bob", 2},
{"Charlie", 3}
};
// 遍历并打印数组内容
for (int i = 0; i < 3; i++) {
printf("姓名: %s, ID: %d\n", people[i].name, people[i].id);
}
return 0;
}
结构体指针
和普通变量一样,你也可以声明指向结构体的指针。
1 声明和初始化
Student student = {"David", 1005, 23, 89.0};
Student *ptr = &student; // ptr 是一个指向 student 变量的指针
2 通过指针访问成员
有两种方式:
-
*使用 `
解引用,然后使用.`**printf("姓名: %s\n", (*ptr).name); -
使用
->(箭头运算符)(更常用、更简洁)->是 的缩写,专门用于通过指针访问结构体成员。printf("姓名: %s\n", ptr->name); printf("学号: %d\n", ptr->id); printf("年龄: %d\n", ptr->age); printf("成绩: %.1f\n", ptr->score);
结构体作为函数参数
结构体可以作为函数的参数传递,主要有两种方式:
1 传值(Pass-by-Value)
将结构体的副本传递给函数,函数内部对结构体的修改不会影响到原始结构体,当结构体很大时,这种方式效率较低,因为需要复制大量数据。
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
// 函数接收一个 Point 结构体的副本
void printPoint(Point p) {
printf("Point: (%d, %d)\n", p.x, p.y);
}
// 函数修改副本,不影响原始变量
void movePoint(Point p) {
p.x += 10;
p.y += 10;
printf("Inside movePoint: (%d, %d)\n", p.x, p.y);
}
int main() {
Point myPoint = {5, 5};
printPoint(myPoint);
movePoint(myPoint); // 传递副本
printf("After movePoint: (%d, %d)\n", myPoint.x, myPoint.y); // myPoint 保持不变
return 0;
}
2 传指针(Pass-by-Pointer/Reference)
将结构体的地址传递给函数,函数可以通过指针直接修改原始结构体,这种方式效率更高,因为它只传递一个地址(通常是 4 或 8 字节),无论结构体多大。
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
// 函数接收一个 Point 结构体的指针
void printPointPtr(const Point *p) { // 使用 const 表示不通过指针修改数据
printf("Point: (%d, %d)\n", p->x, p->y);
}
// 函数通过指针修改原始结构体
void movePointPtr(Point *p) {
p->x += 10;
p->y += 10;
}
int main() {
Point myPoint = {5, 5};
printPointPtr(&myPoint);
movePointPtr(&myPoint); // 传递地址
printf("After movePointPtr: (%d, %d)\n", myPoint.x, myPoint.y); // myPoint 被修改了
return 0;
}
最佳实践:为了避免不必要的拷贝并允许函数修改原始数据,通常推荐传递结构体指针,如果函数只需要读取结构体内容而不修改,可以在指针参数前加上 const 关键字,这是一个非常好的编程习惯。
结构体的大小与内存对齐
结构体的大小不一定是其所有成员大小之和,这涉及到内存对齐问题。
为什么需要内存对齐?
- 平台原因:不是所有的硬件平台都能在任何地址上访问任意类型的数据,某些硬件平台只能在特定地址(如偶数地址)访问特定类型的数据。
- 性能原因:经过内存对齐的数据,CPU 可以一次性读取一个完整的数据,而不需要多次读取和拼接,从而提高访问效率。
规则(简化版):
- 结构体的第一个成员 offset 为 0。
- 其他成员的 offset 是其自身大小的整数倍。
- 结构体的总大小必须是最大成员大小的整数倍。
示例:
#include <stdio.h>
struct A {
char c; // 1 字节
int i; // 4 字节
}; // 大小应该是 8 字节 (1 + 填充3 + 4)
struct B {
int i; // 4 字节
char c; // 1 字节
short s; // 2 字节
}; // 大小应该是 12 字节 (4 + 1 + 填充1 + 2)
struct C {
char c1; // 1
char c2; // 1
int i; // 4
}; // 大小应该是 8 字节 (1 + 1 + 填充2 + 4)
int main() {
printf("sizeof(struct A): %zu\n", sizeof(struct A)); // 输出 8
printf("sizeof(struct B): %zu\n", sizeof(struct B)); // 输出 12
printf("sizeof(struct C): %zu\n", sizeof(struct C)); // 输出 8
return 0;
}
如何优化结构体大小?
将小的数据类型放在一起,大的数据类型放在一起,可以减少填充字节,从而节省内存。struct C 就比 struct A 更紧凑。
| 特性 | 描述 | 示例 |
|---|---|---|
| 定义 | 创建一个自定义的复合数据类型模板。 | struct Student {...}; |
| 声明变量 | 使用定义的类型创建变量。 | Student s1; |
| 成员访问 | 使用 运算符访问变量成员。 | s1.name = "Tom"; |
| 指针访问 | 使用 -> 运算符通过指针访问成员。 |
ptr->age = 20; |
| 函数参数 | 推荐使用指针传参以提高效率,const 用于只读场景。 |
void func(const Student *s); |
| 内存布局 | 需要考虑内存对齐,会影响结构体的大小。 | sizeof(struct) |
struct 是 C 语言进行数据组织和建模的基石,掌握它是从 C 语言初级迈向中级的必经之路,它使得代码更加清晰、模块化,并且更贴近现实世界的问题。
