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;此后不要再访问其元素。这些选择回答了关于声明数组、运行时初始化、在函数中组织设置、以及释放已分配存储的常见问题。