21、结构体(一):结构体的定义与声明、结构体变量的初始化与访问、结构体数组

结构体,说白了就是C语言里用来“打包”数据的工具。你想想看,我们之前学的int、char、float,每个变量只能存一种类型的数据。但现实世界里的东西,哪有这么简单?比如一个学生,他既有学号(整数),又有姓名(字符串),还有成绩(浮点数)。这时候,结构体就派上用场了。

我个人习惯把结构体理解成“自定义的集装箱”。你可以把不同种类的数据装进去,然后整个儿搬来搬去。嗯,这个比喻虽然糙了点,但道理是通的。

核心概念:结构体是一种构造数据类型,它允许你将多个不同类型的数据项组合成一个单一的类型。

结构体的定义与声明

先看怎么定义一个结构体。语法其实不复杂,用 struct 关键字开头,后面跟着结构体名字,然后用花括号把成员变量包起来。

// 定义一个学生结构体类型
struct Student {
    int id;          // 学号
    char name[20];   // 姓名
    float score;     // 成绩
};  // 注意这个分号,不能丢!

我在项目中遇到过好几次,新手写结构体定义时忘了末尾的分号。编译器报错还一脸懵。记住,结构体定义是一个完整的语句,分号是必须的。

定义好结构体类型之后,就可以用它来声明变量了。有三种方式:

  1. 先定义类型,再声明变量——最常用的方式
  2. 定义类型的同时声明变量——适合一次性使用
  3. 使用typedef取别名——我个人最喜欢的方式
// 方式1:先定义类型,再声明变量
struct Student stu1;

// 方式2:定义类型的同时声明变量
struct Teacher {
    int id;
    char name[20];
} t1, t2;  // 这里直接声明了两个变量

// 方式3:使用typedef取别名(推荐)
typedef struct {
    int id;
    char name[20];
    float score;
} Student;  // 以后直接用 Student 声明变量,不用写 struct 关键字了

Student stu2;

我的建议:在大型项目中,尽量用typedef给结构体取别名。这样代码更简洁,而且当你需要修改结构体类型时,只需要改一处定义就行。我在维护一个老项目时,发现满屏的 struct xxx,改起来真是欲哭无泪。

结构体变量的初始化与访问

声明了结构体变量,接下来就是给它赋值。初始化有两种常见方式:

// 方式1:定义时直接初始化(顺序初始化)
Student stu1 = {1001, "张三", 89.5};

// 方式2:指定成员初始化(C99标准支持,更安全)
Student stu2 = {
    .id = 1002,
    .name = "李四",
    .score = 92.0
};

访问结构体成员,用点号 . 操作符。如果是结构体指针,用箭头 -> 操作符。

// 直接访问
printf("学号:%d\n", stu1.id);
printf("姓名:%s\n", stu1.name);
printf("成绩:%.1f\n", stu1.score);

// 通过指针访问
Student *p = &stu1;
printf("学号:%d\n", p->id);   // 等价于 (*p).id
printf("姓名:%s\n", p->name);
printf("成绩:%.1f\n", p->score);

注意:结构体变量之间可以直接赋值!这是C语言的一个特性。比如 stu2 = stu1; 会把stu1的所有成员值拷贝给stu2。但要注意,如果结构体里有指针成员,这种浅拷贝可能会带来问题。我曾经在项目中因为结构体里有个char*指针,直接赋值后两个变量指向同一块内存,改一个另一个也跟着变了……嗯,那是个让人印象深刻的bug。

结构体数组

一个学生好办,但一个班有50个学生怎么办?这时候就需要结构体数组了。说白了,就是把结构体当成一种普通类型,然后定义数组。

// 定义一个包含3个学生的数组
Student class[3] = {
    {1001, "张三", 89.5},
    {1002, "李四", 92.0},
    {1003, "王五", 78.5}
};

// 遍历访问
for (int i = 0; i < 3; i++) {
    printf("学号:%d,姓名:%s,成绩:%.1f\n",
           class[i].id, class[i].name, class[i].score);
}

结构体数组的用法和普通数组一模一样。你可以通过下标访问某个元素,然后通过点号访问它的成员。也可以对数组进行排序、查找等操作。

我举个例子,按成绩排序:

// 冒泡排序,按成绩从高到低
for (int i = 0; i < 3 - 1; i++) {
    for (int j = 0; j < 3 - 1 - i; j++) {
        if (class[j].score < class[j+1].score) {
            Student temp = class[j];
            class[j] = class[j+1];
            class[j+1] = temp;
        }
    }
}

你看,结构体变量可以直接交换,不用一个个成员去赋值。这就是结构体带来的便利。

避坑指南:我曾经在嵌入式项目中,用结构体数组存储传感器数据。一开始没注意结构体的大小,结果数组定义得太大,直接把栈给撑爆了。后来改用静态分配或者动态内存分配才解决。记住,结构体数组占用的内存 = 单个结构体大小 × 数组长度,算清楚再下手。

知识体系总览

下面这张图,把结构体的核心知识点串起来了。你可以把它当作一个思维导图来看。

结构体 定义与声明 先定义类型,再声明变量 定义类型同时声明变量 typedef 取别名(推荐) 初始化与访问 顺序初始化 / 指定初始化 点号 . 直接访问 箭头 -> 指针访问 结构体数组

这张图把结构体的三大块内容——定义与声明、初始化与访问、结构体数组——清晰地展示出来了。你可以看到,每个分支下面还有更细的子知识点。学习的时候,按这个脉络走,不会乱。

最后说一句,结构体是C语言里非常实用的工具。从简单的数据打包,到复杂的链表、树等数据结构,都离不开它。把这一章吃透了,后面的路就好走了。

专注资料整理