第23章:结构体基础——结构体的定义、声明、初始化,访问成员,结构体数组
说实话,C语言里我最喜欢的数据类型就是结构体。为什么?因为它让代码有了“灵魂”。
你想想看,之前我们学的int、char、float,都是单一的数据。但现实世界里的东西,哪有这么简单?一本书有书名、作者、价格、ISBN号;一个学生有学号、姓名、成绩、年龄。用单独的变量去描述,代码会乱成一锅粥。
结构体就是用来解决这个问题的。它把多个不同类型的数据打包成一个整体。嗯,就像把散落一地的零件装进一个工具箱。
23.1 结构体的定义
定义结构体,说白了就是“画图纸”。你告诉编译器:我要造一种新类型,它长这样。
// 定义一个"学生"结构体类型
struct Student {
int id; // 学号
char name[20]; // 姓名
float score; // 成绩
int age; // 年龄
}; // 注意这个分号!我见过太多人漏掉它
这里有几个要点:
- struct 是关键字,告诉编译器我要定义结构体
- Student 是结构体标签,相当于这个“图纸”的名字
- 花括号里是成员变量,每个成员都有自己的类型和名字
- 最后的分号不能丢——我曾经因为这个分号被编译器骂了半小时
23.2 结构体的声明
图纸画好了,接下来就是“造东西”。声明一个结构体变量,就像按照图纸造一个实物。
// 方式一:先定义类型,再声明变量
struct Student stu1; // 声明一个Student类型的变量stu1
struct Student stu2; // 再声明一个
// 方式二:定义类型的同时声明变量
struct Student {
int id;
char name[20];
float score;
int age;
} stu3, stu4; // 这里直接声明了两个变量
// 方式三:匿名结构体(不常用,但面试可能会问)
struct {
int x;
int y;
} point; // 这种结构体类型没有名字,只能在这里用一次
我个人最常用的是方式一。代码清晰,别人一看就懂。方式二虽然省事,但可读性差一些。
23.3 结构体的初始化
变量声明了,总得给个初值吧?结构体的初始化有好几种玩法。
// 方式一:按成员顺序初始化
struct Student stu1 = {1001, "张三", 89.5, 20};
// 方式二:指定成员初始化(C99标准支持)
struct Student stu2 = {
.id = 1002,
.name = "李四",
.score = 92.0,
.age = 21
};
// 方式三:部分初始化(未指定的成员自动为0)
struct Student stu3 = {1003, "王五"}; // score=0.0, age=0
// 方式四:先声明后赋值(注意:不能整体赋值!)
struct Student stu4;
stu4.id = 1004;
strcpy(stu4.name, "赵六"); // 字符串要用strcpy
stu4.score = 78.5;
stu4.age = 19;
stu4 = {1005, "xxx", 90, 22}; 是错的!必须逐个成员赋值。
我在项目中遇到过一个问题:有个同事用方式一初始化,后来结构体成员顺序调整了,所有初始化代码全崩了。从那以后,我强烈推荐方式二——指定成员初始化。顺序变了也不怕,代码还更清晰。
23.4 访问结构体成员
结构体造好了,怎么用里面的数据?用点号(.)操作符。
struct Student stu = {1001, "张三", 89.5, 20};
// 访问成员
printf("学号:%d\n", stu.id);
printf("姓名:%s\n", stu.name);
printf("成绩:%.1f\n", stu.score);
printf("年龄:%d\n", stu.age);
// 修改成员
stu.score = 95.0; // 成绩更新了
strcpy(stu.name, "张三丰"); // 改名了
如果是结构体指针,就要用箭头(->)操作符。这个后面讲指针时会细说,先留个印象:
struct Student *p = &stu;
printf("成绩:%.1f\n", p->score); // 等价于 (*p).score
23.5 结构体数组
一个学生用结构体,那一个班50个学生呢?总不能写50个变量吧?这时候结构体数组就派上用场了。
// 声明一个能存50个学生的数组
struct Student class[50];
// 初始化结构体数组
struct Student class[3] = {
{1001, "张三", 89.5, 20},
{1002, "李四", 92.0, 21},
{1003, "王五", 78.5, 19}
};
// 访问数组中的结构体成员
for (int i = 0; i < 3; i++) {
printf("第%d个学生:%s,成绩:%.1f\n",
i+1, class[i].name, class[i].score);
}
// 修改某个学生的信息
class[1].score = 95.5; // 李四成绩改高了
结构体数组的操作和普通数组一模一样,只是每个元素是一个结构体。你想想看,用数组存50个int,和用结构体数组存50个学生,哪个更贴近现实?
typedef struct Student {
int id;
char name[20];
float score;
int age;
} Stu; // 以后直接用 Stu 代替 struct Student
Stu class[50]; // 简洁多了
23.6 知识体系总览
下面这张图把结构体的核心知识点串起来了。我画图时习惯把“定义”放在最上面,因为它是基础中的基础。
23.7 避坑指南
做嵌入式这些年,结构体相关的坑我踩过不少。挑几个典型的说说:
- 结构体对齐问题:编译器会在结构体成员之间插入填充字节,保证对齐。比如char后面跟int,char占1字节,但int要从4字节对齐地址开始,中间会空3个字节。用
sizeof()算出来的大小往往比你想的大。我曾在通信协议里用结构体直接打包数据,结果两端对齐方式不同,数据全解析错了。 - 不要用memcmp比较结构体:因为填充字节的值是不确定的,直接内存比较可能得到错误结果。应该逐个成员比较。
- 结构体赋值是浅拷贝:如果结构体里有指针成员,直接赋值只是复制了指针值,两个结构体指向同一块内存。修改一个,另一个也跟着变。这个坑我帮别人排查过好几次。
结构体是C语言里组织数据的核心工具。说白了,它就是让你用代码描述现实世界的东西。学生、图书、坐标、传感器数据……任何有多个属性的东西,都可以用结构体来表达。嗯,掌握了结构体,你的C语言水平会上一个大台阶。
公众号:蓝海资料掘金营,微信deep3321