核心概念
结构体 (struct) 允许你将不同类型的数据组合成一个单一的、自定义的类型,当结构体中包含一个数组作为成员时,就意味着这个结构体可以“持有”一个固定大小的同类型数据集合。

(图片来源网络,侵删)
基本定义和声明
定义一个包含数组成员的结构体,语法与定义普通成员类似,只需在成员名后加上数组的维度(大小)即可。
语法格式:
struct 结构体名 {
数据类型 成员名1;
数据类型 成员名2[数组大小]; // 这就是一个数组成员
// ... 其他成员
};
示例:学生信息结构体
假设我们要表示一个学生的信息,包括学号、姓名和3门课程的分数,我们可以这样定义结构体:

(图片来源网络,侵删)
#include <stdio.h>
#include <string.h> // 用于 strcpy 函数
// 定义学生结构体
struct Student {
int id; // 学号
char name[50]; // 姓名,使用字符数组来存储
float scores[3]; // 3门课程的分数,使用浮点型数组
};
int main() {
// 声明一个结构体变量 s1
struct Student s1;
// 访问和赋值数组成员
s1.id = 1001;
strcpy(s1.name, "张三"); // 使用 strcpy 安全地复制字符串到数组
// 访问数组成员中的单个元素
s1.scores[0] = 95.5f; // 第一门课分数
s1.scores[1] = 88.0f; // 第二门课分数
s1.scores[2] = 92.5f; // 第三门课分数
// 打印结构体信息
printf("学号: %d\n", s1.id);
printf("姓名: %s\n", s1.name);
printf("成绩1: %.2f\n", s1.scores[0]);
printf("成绩2: %.2f\n", s1.scores[1]);
printf("成绩3: %.2f\n", s1.scores[2]);
return 0;
}
关键点:
- 访问方式:使用结构体变量名 + 成员运算符 () + 数组名,然后就可以像普通数组一样使用下标 (
[]) 来访问或修改元素。 - 字符串:
char name[50]是一个经典的用法,用于存储固定长度的字符串。切记不能直接用 赋值,必须使用strcpy()或strncpy()等函数。
结构体数组 vs. 结构体中的数组
这是一个非常重要的概念,初学者很容易混淆。
A. 结构体数组
这是一个数组,它的每个元素都是一个结构体。
- 定义:
struct Student class[30]; - 含义:定义了一个名为
class的数组,它可以存放 30 个Student类型的结构体。 - 访问:通过数组下标找到某个结构体,再用 访问其成员。
class[0].id = 1001;// 给第一个学生的学号赋值class[29].scores[0] = 90.0f;// 给最后一个学生的第一门课成绩赋值
B. 结构体中的数组
这是一个结构体,它包含一个数组作为其成员。

(图片来源网络,侵删)
- 定义:如上面的
struct Student,它内部有scores[3]。 - 含义:每个
Student对象都自带一个能存3个分数的数组。 - 访问:先找到结构体,再用 访问其数组成员,然后用
[]访问数组元素。s1.scores[0] = 95.5f;// 给 s1 这个学生的第一门课成绩赋值
内存布局与 sizeof
了解结构体中数组的内存布局有助于你更深刻地理解它。
编译器会为结构体的每个成员分配连续的内存空间,并遵循内存对齐规则。
示例:
struct Data {
char c; // 1 字节
int arr[3]; // 4 * 3 = 12 字节
double d; // 8 字节
};
假设 sizeof(int) = 4, sizeof(double) = 8,这个结构体的内存布局大致如下(简化,不考虑对齐填充):
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| c | | | | arr[0] | | | | arr[1] | | | | arr[2] | | | | d |
| | | | | (4字节) | | | | (4字节) | | | | (4字节) | | | | (8字节) |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
sizeof(struct Data)的结果通常是1 + 12 + 8 = 21字节,但因为有内存对齐(char后面可能填充3字节,int数组后填充4字节),实际大小可能是 32 字节,你可以用printf("%zu\n", sizeof(struct Data));来验证。
初始化
结构体中的数组成员可以在声明时进行初始化,方法与初始化普通数组类似。
struct Student s2 = {
.id = 1002,
.name = "李四", // 编译器会自动处理为字符串字面量
.scores = {85.0f, 76.5f, 91.0f} // 使用初始化列表
};
// 或者使用更清晰的点号表示法
struct Student s3 = {
.id = 1003,
.name = "王五",
.scores = {60.0f, 70.0f, 80.0f}
};
作为函数参数传递
将包含数组成员的结构体传递给函数时,有几种方式,它们的效率和用法不同。
A. 按值传递(直接传递结构体变量)
特点:
- 安全:函数内部对结构体的修改不会影响原始结构体。
- 效率低:如果结构体很大(比如包含大数组),复制整个结构体会消耗大量时间和内存。
// 函数声明
void printStudent(struct Student s);
// 函数定义
void printStudent(struct Student s) {
printf("学号: %d, 姓名: %s\n", s.id, s.name);
printf("成绩: %.2f, %.2f, %.2f\n", s.scores[0], s.scores[1], s.scores[2]);
}
// 调用
printStudent(s1);
B. 按地址传递(传递结构体指针)
特点:
- 高效:只传递一个指针(通常是4或8字节),无论结构体多大。
- 不安全:函数内部可以通过指针修改原始结构体的内容,如果不需要修改,应使用
const关键字来保护原始数据。
// 函数声明 (推荐使用 const 如果不打算修改)
void printStudentByPointer(const struct Student *ps);
// 函数定义
void printStudentByPointer(const struct Student *ps) {
// 通过指针访问成员,使用 -> 运算符
printf("学号: %d, 姓名: %s\n", ps->id, ps->name);
printf("成绩: %.2f, %.2f, %.2f\n", ps->scores[0], ps->scores[1], ps->scores[2]);
}
// 调用
printStudentByPointer(&s1);
最佳实践:优先使用按地址传递,并用 const 修饰,除非结构体非常小且你需要副本。
| 特性 | 说明 |
|---|---|
| 定义 | 在结构体内部声明 类型 数组名[大小]; 即可。 |
| 访问 | 结构体变量.数组名[下标],s1.scores[0]。 |
| 用途 | 将一个集合数据(如成绩、坐标点、RGB颜色值)与单个实体(如学生、物体)关联起来。 |
| 内存 | 数组成员是结构体的一部分,与结构体其他成员连续存储(考虑对齐)。 |
| 初始化 | 可以在声明时使用初始化列表 进行赋值。 |
| 参数传递 | 推荐:传递 const struct Student*,高效且安全,避免直接传递大型结构体值。 |
掌握结构体的数组成员是 C 语言进行复杂数据建模的基础,比如游戏开发中的物体列表、网络编程中的数据包结构等。
