HOWTO · C
在 C 语言中初始化结构体数组
在 C 语言中用大括号列表、运行时分配,或函数加循环来初始化结构体数组,并附内存释放与安全字符串拷贝的说明。
本页内容
当数组的值在声明处已知时,使用大括号初始化器。如果数组的大小或值来自运行时数据,则分配存储并逐个元素赋值,或者把数组传给一个填充它的函数。在每种情况下,初始化器中的值都遵循成员的声明顺序。
在 C 语言中,struct 把相关字段归到一个类型之下。这里,每个 Student 都有姓名、年龄和 GPA。
结构体与数组初始化基础
先声明类型,再声明该类型的数组。关键字 struct 引入记录类型,结构体初始化器按声明顺序列出其字段:name,然后 age,然后 gpa。用于姓名的字符数组会存储一个结尾的空字符,普通字符串字面量会自动包含它。
用初始化列表初始化固定大小的数组
使用嵌套的大括号,为数组的每个元素提供一个 Student 初始化器。这个完整示例使用源代码中已知的值,并打印每条初始化后的记录。
运行此示例:
#include <stdio.h>
struct Student {
char name[50];
int age;
float gpa;
};
int main(void) {
struct Student students[3] = {
{"Alice", 20, 3.5f},
{"Bob", 22, 3.8f},
{"Charlie", 19, 3.2f}
};
for (int i = 0; i < 3; ++i) {
printf("%s: age %d, GPA %.2f\n",
students[i].name, students[i].age, students[i].gpa);
}
return 0;
}
Alice: age 20, GPA 3.50
Bob: age 22, GPA 3.80
Charlie: age 19, GPA 3.20
在运行时分配并初始化数组
当元素数量在程序运行期间确定时,为该数量的结构体分配足够的存储。malloc 预留存储,但不会给字段赋予有意义的值,因此在读取每个元素之前先填充它。检查返回的指针,然后在数组不再需要时用 free 释放它。
这个完整示例在运行时选择数量,并逐条赋值记录。
#include <stdio.h>
#include <stdlib.h>
struct Student {
char name[50];
int age;
float gpa;
};
int main(void) {
size_t count = 3;
struct Student *students = malloc(count * sizeof *students);
if (students == NULL) {
fputs("Memory allocation failed\n", stderr);
return 1;
}
students[0] = (struct Student){"Alice", 20, 3.5f};
students[1] = (struct Student){"Bob", 22, 3.8f};
students[2] = (struct Student){"Charlie", 19, 3.2f};
for (size_t i = 0; i < count; ++i) {
printf("%s: age %d, GPA %.2f\n",
students[i].name, students[i].age, students[i].gpa);
}
free(students);
return 0;
}
Alice: age 20, GPA 3.50
Bob: age 22, GPA 3.80
Charlie: age 19, GPA 3.20
用于单个赋值的复合字面量需要 C99 或更高版本。如果分配失败,程序会报告失败,并在访问数组之前退出。
用函数和循环初始化数组元素
当把重复的初始化移到函数中能让调用代码更清晰时,就这样做。对于固定大小的字符串,在拷贝之前检查源的长度:strlen(source) < sizeof destination 会为结尾的空字符留出空间。下面的函数会报告过长的姓名并且不拷贝它;发生这种情况时调用者停止。
这个完整示例从一组平行的姓名、年龄和 GPA 初始化每条记录。
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float gpa;
};
static int initialize_student(struct Student *student,
const char *source,
int age,
float gpa) {
char destination[50];
size_t length = strlen(source);
if (length >= sizeof destination) {
fputs("Name is too long\n", stderr);
return 0;
}
memcpy(destination, source, length + 1);
strcpy(student->name, destination);
student->age = age;
student->gpa = gpa;
return 1;
}
int main(void) {
const char *names[] = {"Alice", "Bob", "Charlie"};
const int ages[] = {20, 22, 19};
const float gpas[] = {3.5f, 3.8f, 3.2f};
struct Student students[3];
for (size_t i = 0; i < 3; ++i) {
if (!initialize_student(&students[i], names[i], ages[i], gpas[i])) {
return 1;
}
}
for (size_t i = 0; i < 3; ++i) {
printf("%s: age %d, GPA %.2f\n",
students[i].name, students[i].age, students[i].gpa);
}
return 0;
}
Alice: age 20, GPA 3.50
Bob: age 22, GPA 3.80
Charlie: age 19, GPA 3.20
长度测试使用 >= 作为拒绝条件,这等价于要求在拷贝之前满足 strlen(source) < sizeof destination。memcpy 只有在这个检查之后,才拷贝所测量的字符及其终止符。
选择合适的初始化方法
- 当数组大小和值在源代码中已知时,选择大括号初始化器。当你想稍后再赋给元素值时,用
struct Student students[3];声明数组。 - 当元素数量在程序运行期间获得时,选择运行时分配。记住,分配只提供存储:初始化字段,并在用完后调用
free。 - 当初始化逻辑需要复用,或要与主流程分开保存时,选择函数加循环。在把输入字符串拷贝到固定大小的成员之前先验证它们。
具有自动存储期的数组,在其所在作用域结束时被释放。用 malloc 获得的数组会一直保持分配状态,直到把它传给 free;此后不要再访问其元素。这些选择回答了关于声明数组、运行时初始化、在函数中组织设置、以及释放已分配存储的常见问题。