21、结构体(一):结构体的定义与声明、结构体变量的初始化与访问、结构体数组
结构体,说白了就是C语言里用来“打包”数据的工具。你想想看,我们之前学的int、char、float,每个变量只能存一种类型的数据。但现实世界里的东西,哪有这么简单?比如一个学生,他既有学号(整数),又有姓名(字符串),还有成绩(浮点数)。这时候,结构体就派上用场了。
我个人习惯把结构体理解成“自定义的集装箱”。你可以把不同种类的数据装进去,然后整个儿搬来搬去。嗯,这个比喻虽然糙了点,但道理是通的。
核心概念:结构体是一种构造数据类型,它允许你将多个不同类型的数据项组合成一个单一的类型。
结构体的定义与声明
先看怎么定义一个结构体。语法其实不复杂,用 struct 关键字开头,后面跟着结构体名字,然后用花括号把成员变量包起来。
// 定义一个学生结构体类型
struct Student {
int id; // 学号
char name[20]; // 姓名
float score; // 成绩
}; // 注意这个分号,不能丢!
我在项目中遇到过好几次,新手写结构体定义时忘了末尾的分号。编译器报错还一脸懵。记住,结构体定义是一个完整的语句,分号是必须的。
定义好结构体类型之后,就可以用它来声明变量了。有三种方式:
- 先定义类型,再声明变量——最常用的方式
- 定义类型的同时声明变量——适合一次性使用
- 使用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;
}
}
}
你看,结构体变量可以直接交换,不用一个个成员去赋值。这就是结构体带来的便利。
避坑指南:我曾经在嵌入式项目中,用结构体数组存储传感器数据。一开始没注意结构体的大小,结果数组定义得太大,直接把栈给撑爆了。后来改用静态分配或者动态内存分配才解决。记住,结构体数组占用的内存 = 单个结构体大小 × 数组长度,算清楚再下手。
知识体系总览
下面这张图,把结构体的核心知识点串起来了。你可以把它当作一个思维导图来看。
这张图把结构体的三大块内容——定义与声明、初始化与访问、结构体数组——清晰地展示出来了。你可以看到,每个分支下面还有更细的子知识点。学习的时候,按这个脉络走,不会乱。
最后说一句,结构体是C语言里非常实用的工具。从简单的数据打包,到复杂的链表、树等数据结构,都离不开它。把这一章吃透了,后面的路就好走了。