MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言结构体初始化的不同场景

2022-01-116.2k 阅读

定义结构体

在探讨结构体初始化的不同场景之前,先来回顾一下如何定义结构体。在C语言中,结构体是一种用户自定义的数据类型,它允许将不同类型的数据组合在一起。例如,定义一个表示日期的结构体:

struct Date {
    int year;
    int month;
    int day;
};

这里,struct Date 就是一个结构体类型,它包含了三个成员:yearmonthday,都是 int 类型。

结构体变量的声明

在定义了结构体类型后,就可以声明该类型的变量。声明结构体变量的方式和声明普通变量类似,例如:

struct Date today;

这里声明了一个 struct Date 类型的变量 today

结构体初始化的基本方式

初始化列表方式

在C语言中,最常见的结构体初始化方式是使用初始化列表。对于上述的 struct Date 结构体,可以这样初始化:

struct Date today = {2023, 10, 1};

在这个初始化列表中,花括号 {} 内的值按照结构体成员的顺序依次对应。即 2023 对应 year10 对应 month1 对应 day

如果结构体成员较多,也可以按照这种方式依次初始化:

struct Point {
    int x;
    int y;
    int z;
};
struct Point origin = {0, 0, 0};

这里 originstruct Point 类型的变量,通过初始化列表将其 xyz 成员都初始化为 0

部分初始化

在初始化结构体时,也可以只初始化部分成员。未初始化的成员会根据其类型被赋予默认值。对于 int 类型,默认值为 0;对于 float 类型,默认值为 0.0;对于指针类型,默认值为 NULL。例如:

struct Date tomorrow = {2023, 10};

这里 tomorrowyear 被初始化为 2023month 被初始化为 10,而 day 则被默认初始化为 0

按成员初始化

除了使用初始化列表,还可以在声明变量后,逐个对结构体成员进行初始化。例如:

struct Date holiday;
holiday.year = 2024;
holiday.month = 1;
holiday.day = 1;

这种方式更加灵活,特别是在需要根据程序运行的逻辑来确定初始值的情况下。比如,根据用户输入来初始化结构体变量:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

int main() {
    struct Date userDate;
    printf("请输入年:");
    scanf("%d", &userDate.year);
    printf("请输入月:");
    scanf("%d", &userDate.month);
    printf("请输入日:");
    scanf("%d", &userDate.day);
    printf("你输入的日期是:%d-%d-%d\n", userDate.year, userDate.month, userDate.day);
    return 0;
}

在这个程序中,通过 scanf 函数获取用户输入的值,然后逐个初始化 userDate 结构体变量的成员。

结构体嵌套时的初始化

嵌套结构体的定义

当一个结构体的成员又是另一个结构体类型时,就形成了嵌套结构体。例如,定义一个表示地址的结构体,再定义一个表示人的结构体,其中包含地址结构体作为成员:

struct Address {
    char city[20];
    char street[30];
    int zipCode;
};

struct Person {
    char name[20];
    int age;
    struct Address addr;
};

这里 struct Person 结构体包含了一个 struct Address 类型的成员 addr

嵌套结构体的初始化

对于嵌套结构体的初始化,同样可以使用初始化列表。初始化列表需要按照结构体成员的层次结构来组织。例如:

struct Person john = {
    "John Doe",
    30,
    {"New York", "123 Main St", 10001}
};

在这个初始化列表中,首先是 johnname 成员被初始化为 "John Doe"age 成员被初始化为 30,然后 addr 这个嵌套结构体成员按照其自身的成员顺序进行初始化,city"New York"street"123 Main St"zipCode10001

也可以在声明后逐个初始化嵌套结构体的成员:

struct Person jane;
strcpy(jane.name, "Jane Smith");
jane.age = 25;
strcpy(jane.addr.city, "Los Angeles");
strcpy(jane.addr.street, "456 Elm St");
jane.addr.zipCode = 90001;

这里通过 strcpy 函数来复制字符串到结构体成员中,因为结构体中的字符串成员是字符数组,不能直接使用赋值语句。

结构体数组的初始化

结构体数组的定义

结构体数组是指数组的每个元素都是一个结构体类型。例如,定义一个包含多个日期的结构体数组:

struct Date {
    int year;
    int month;
    int day;
};
struct Date dates[3];

这里 dates 是一个包含 3struct Date 类型元素的数组。

结构体数组的初始化

  1. 使用初始化列表:可以使用大括号括起来的多个初始化列表来初始化结构体数组。每个内部的初始化列表对应数组中的一个元素。例如:
struct Date dates[3] = {
    {2023, 10, 1},
    {2023, 10, 2},
    {2023, 10, 3}
};

这里 dates[0] 被初始化为 {2023, 10, 1}dates[1] 被初始化为 {2023, 10, 2}dates[2] 被初始化为 {2023, 10, 3}

  1. 部分初始化:和单个结构体变量类似,结构体数组也可以进行部分初始化。未初始化的元素会根据结构体成员类型赋予默认值。例如:
struct Date moreDates[3] = {
    {2023, 10, 1}
};

这里 moreDates[0] 被初始化为 {2023, 10, 1}moreDates[1]moreDates[2]yearmonthday 成员都被默认初始化为 0

  1. 逐个初始化:也可以在声明结构体数组后,逐个初始化数组中的每个结构体元素的成员。例如:
struct Date someDates[3];
someDates[0].year = 2023;
someDates[0].month = 10;
someDates[0].day = 1;
someDates[1].year = 2023;
someDates[1].month = 10;
someDates[1].day = 2;
someDates[2].year = 2023;
someDates[2].month = 10;
someDates[2].day = 3;

这种方式适合在需要根据不同的逻辑来初始化每个结构体元素的场景。

动态分配内存的结构体初始化

使用 malloc 分配内存

在C语言中,可以使用 malloc 函数为结构体动态分配内存。例如,为 struct Date 结构体动态分配内存:

#include <stdlib.h>
struct Date {
    int year;
    int month;
    int day;
};
struct Date *dynamicDate = (struct Date *)malloc(sizeof(struct Date));

这里通过 malloc 函数分配了一块大小为 struct Date 结构体大小的内存,并将返回的指针强制转换为 struct Date * 类型,赋值给 dynamicDate 指针。

初始化动态分配的结构体

  1. 使用初始化列表(C99及以后):在C99标准及以后,可以使用复合字面量来初始化动态分配的结构体。复合字面量是一种临时的数组或结构体值,它的类型是未命名的数组或结构体类型。例如:
struct Date *dynamicDate = (struct Date *)malloc(sizeof(struct Date));
*dynamicDate = (struct Date){2023, 10, 1};

这里通过复合字面量 (struct Date){2023, 10, 1} 创建了一个临时的 struct Date 结构体值,然后将其赋值给 *dynamicDate,即动态分配的结构体。

  1. 逐个初始化:也可以像普通结构体变量一样,在动态分配内存后逐个初始化结构体成员。例如:
struct Date *dynamicDate = (struct Date *)malloc(sizeof(struct Date));
dynamicDate->year = 2023;
dynamicDate->month = 10;
dynamicDate->day = 1;

这里使用 -> 运算符来访问指针指向的结构体的成员并进行初始化。

释放动态分配的内存

在使用完动态分配的结构体后,需要使用 free 函数释放内存,以避免内存泄漏。例如:

free(dynamicDate);
dynamicDate = NULL;

这里先调用 free 函数释放 dynamicDate 指向的内存,然后将 dynamicDate 赋值为 NULL,防止成为悬空指针。

结构体指针的初始化

结构体指针的声明与初始化

结构体指针是指向结构体变量的指针。声明结构体指针并初始化它指向一个已存在的结构体变量的方式如下:

struct Date {
    int year;
    int month;
    int day;
};
struct Date today = {2023, 10, 1};
struct Date *datePtr = &today;

这里 datePtr 是一个 struct Date * 类型的指针,通过取地址符 & 使其指向 today 结构体变量。

通过指针访问和初始化结构体成员

通过结构体指针访问和初始化结构体成员有两种方式:使用 -> 运算符或通过解引用指针后使用 . 运算符。例如:

// 使用 -> 运算符
datePtr->year = 2024;
// 使用解引用指针和. 运算符
(*datePtr).month = 11;
(*datePtr).day = 15;

-> 运算符是一种更简洁的写法,它等价于先解引用指针再使用 . 运算符。

联合结构体的初始化

联合结构体的定义

联合(union)也是一种用户自定义的数据类型,它允许在同一内存位置存储不同的数据类型。联合的所有成员共享相同的内存空间,其大小取决于最大成员的大小。例如,定义一个联合结构体:

union Data {
    int num;
    float fnum;
    char ch;
};

这里 union Data 就是一个联合结构体,它包含了一个 int 类型成员 num,一个 float 类型成员 fnum 和一个 char 类型成员 ch

联合结构体的初始化

联合的初始化方式与结构体类似,但由于所有成员共享内存,只能初始化一个成员。例如:

union Data myData = {10};

这里 myData 被初始化为 10,此时 myData.num 的值为 10,如果访问 myData.fnummyData.ch,得到的值是未定义的,因为内存中存储的是 int 类型的 10,以 floatchar 类型去解释可能会得到意想不到的结果。

也可以在声明后初始化联合成员:

union Data anotherData;
anotherData.fnum = 3.14f;

这里先声明了 anotherData,然后将其 fnum 成员初始化为 3.14f

结构体作为函数参数和返回值时的初始化

结构体作为函数参数

当结构体作为函数参数传递时,可以在调用函数时对传递的结构体进行初始化。例如:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

void printDate(struct Date date) {
    printf("%d-%d-%d\n", date.year, date.month, date.day);
}

int main() {
    struct Date today = {2023, 10, 1};
    printDate(today);
    return 0;
}

在这个例子中,today 结构体变量在 main 函数中初始化,然后作为参数传递给 printDate 函数,printDate 函数打印出 today 的日期信息。

结构体作为函数返回值

函数也可以返回一个结构体。在返回结构体时,可以在函数内部对返回的结构体进行初始化。例如:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

struct Date getTomorrow(struct Date today) {
    struct Date tomorrow;
    tomorrow.year = today.year;
    tomorrow.month = today.month;
    tomorrow.day = today.day + 1;
    return tomorrow;
}

int main() {
    struct Date today = {2023, 10, 1};
    struct Date tomorrow = getTomorrow(today);
    printf("明天的日期是:%d-%d-%d\n", tomorrow.year, tomorrow.month, tomorrow.day);
    return 0;
}

getTomorrow 函数中,根据传入的 today 结构体计算并初始化 tomorrow 结构体,然后将 tomorrow 返回给 main 函数。

位域结构体的初始化

位域结构体的定义

位域结构体允许在一个结构体中以位为单位来定义成员。这在节省内存空间以及处理硬件寄存器等场景中非常有用。例如,定义一个表示颜色的位域结构体:

struct Color {
    unsigned int red : 5;
    unsigned int green : 5;
    unsigned int blue : 5;
    unsigned int alpha : 3;
};

这里 redgreenblue 成员分别占用 5 位,alpha 成员占用 3 位。

位域结构体的初始化

位域结构体的初始化和普通结构体类似,可以使用初始化列表。例如:

struct Color myColor = {31, 31, 31, 7};

这里 myColor.red 被初始化为 31(5 位二进制表示为 11111),myColor.green 被初始化为 31myColor.blue 被初始化为 31myColor.alpha 被初始化为 7(3 位二进制表示为 111)。

也可以在声明后逐个初始化位域成员:

struct Color anotherColor;
anotherColor.red = 10;
anotherColor.green = 15;
anotherColor.blue = 20;
anotherColor.alpha = 3;

这样就分别初始化了 anotherColor 的各个位域成员。

通过以上对C语言结构体初始化不同场景的详细介绍,希望读者能够对结构体初始化有更深入的理解和掌握,在实际编程中能够根据不同的需求选择合适的初始化方式。无论是简单的结构体变量初始化,还是复杂的嵌套结构体、结构体数组、动态分配结构体等的初始化,都有其适用的场景和注意事项。掌握好这些知识,将有助于编写出更高效、更健壮的C语言程序。在实际应用中,比如在嵌入式系统开发中,经常会用到位域结构体来操作硬件寄存器,准确地初始化位域结构体成员对于系统的正确运行至关重要;在数据处理程序中,结构体数组的初始化和操作也是非常常见的任务,合理地初始化结构体数组能够提高数据处理的效率。同时,在使用动态分配内存的结构体时,务必注意内存的释放,以避免内存泄漏等问题。总之,深入理解结构体初始化的各种场景是C语言编程的重要基础。