首先要纠正一个基础认知,struct 本身不是函数,它是 C 语言的自定义复合数据类型关键字,作用是把多个不同类型的数据打包组合成一个新的数据类型,很多初学者习惯叫成 struct 函数,实际是结构体类型,后续可以配合普通函数来操作这个结构体里面的数据。之所以需要 struct,是因为基础类型 int char float 只能单独保存一种数据,现实业务经常需要把相关联的数据放在一起,比如学生信息包含姓名学号成绩,这些数据属于同一个对象,分散定义多个独立变量不方便管理,使用 struct 就可以把它们封装成一套整体类型。使用的时候第一步使用 struct 关键字声明结构体模板,定义里面包含的各个成员变量,第二步可以用这个模板创建结构体变量,第三步给结构体各个成员赋值,第四步编写普通函数,传入结构体变量或者结构体指针,在函数内部读写成员,实现对结构体的业务处理,第五步也可以给结构体搭配 typedef 起别名,简化后续书写。整体理解思路就是 struct 是类型定义工具,函数是处理这个类型数据的代码块,二者是搭配使用的关系,并不是 struct 自带函数,C 语言的结构体不像 C++ 类那样内置成员函数,这点是 C 和 C++ 非常重要的区别。
基础语法
声明结构体模板写法
struct 标签名
{
成员 1 类型 成员 1 名字;
成员 2 类型 成员 2 名字;
成员 3 类型 成员 3 名字;
};
创建变量有三种方式
第一种 声明模板之后单独创建变量
struct Student s1;
第二种 声明模板的时候直接创建变量
struct Student
{
int id;
char name [20];
float score;
} s1;
第三种 使用 typedef 给结构体起别名,后续不用每次写 struct 关键字
typedef struct Student
{
int id;
char name [20];
float score;
} Student;
成员访问方式
普通结构体变量 使用点运算符访问成员
s1.id = 1001;
结构体指针 使用箭头运算符访问成员
Student p = &s1;
p->score = 90.5;
结构体搭配函数示例代码
include
typedef struct Student
{
int id;
char name[20];
float score;
} Student;
void printStudent (Student stu)
{
printf ("学号 % d 姓名 % s 成绩 %.2f\n", stu.id, stu.name, stu.score);
}
void updateScore(Student pstu, float newScore)
{
pstu->score = newScore;
}
int main ()
{
Student s = {1001, "tom", 85};
printStudent (s);
updateScore (&s, 92);
printStudent (s);
return 0;
}
上面代码里面 printStudent 是传值调用,把结构体整体拷贝一份传入函数,函数内部修改副本不会影响外面原始结构体,updateScore 是传指针调用,直接操作原始内存,可以修改外部结构体数据,这是结构体函数设计最核心的知识点。
深度重点知识
第一 内存对齐,struct 里面成员会按照编译器规则做内存对齐,结构体实际占用字节数不一定是所有成员字节相加,会存在填充字节,这个会影响数组嵌入式开发内存占用计算,可以使用 sizeof 计算结构体总大小。
第二 结构体赋值,同类型结构体变量可以直接整体赋值 s2 = s1,本质是按内存逐字节拷贝,但是如果成员里面包含指针,只会拷贝指针地址,不会拷贝指针指向的内容,也就是浅拷贝,容易产生悬空指针问题。
第三 结构体数组,可以定义 Student stuArr [10],批量保存多个同类型结构体对象,配合循环和函数批量处理,这是管理一组对象最常用方案。
第四 嵌套结构体,结构体成员还可以是另外一个 struct 类型,实现层级数据结构,比如学生结构体里面嵌套出生日期结构体。
第五 函数返回结构体,C 语言函数可以直接返回整个结构体变量,但是返回大体积结构体的时候会发生拷贝,性能较差,一般优先使用传入指针的方式。
传值调用和传指针调用对比表
| 对比项目 | 传结构体值 | 传结构体指针 |
|---|---|---|
| 内存行为 | 完整拷贝一份结构体副本 | 只传递地址,不拷贝全部数据 |
| 是否能修改原数据 | 不能修改外部原始结构体 | 可以修改外部原始结构体 |
| 性能 | 结构体很大的时候拷贝开销高 | 开销很小 |
| 访问成员符号 | 点运算符 | 箭头运算符 |
【常见问题】
问题 1 C 语言 struct 函数是不是和 C++ 类一样自带成员函数
回答 1 C 语言 struct 函数并不是内置成员函数,struct 只是自定义复合数据类型,C 语言结构体不能像 C++ 类那样在 struct 内部写函数,只能额外编写普通函数,通过传值或者传指针的方式操作 struct 数据。
问题 2 C 语言 struct 函数传值调用为什么修改不了外部数据
回答 1 C 语言 struct 函数传值调用的时候,会把结构体完整拷贝一份副本传入函数,函数内部操作的是副本内存,外部原始 struct 变量不会发生改变,如果想要修改外部数据,struct 函数就要改成传入结构体指针。
问题 3 C 语言 struct 函数里面箭头运算符和点运算符怎么选择
回答 1 C 语言 struct 函数中,如果参数是普通 struct 变量,使用点运算符访问成员,如果参数是 struct 指针,就要使用箭头运算符访问成员,这是 struct 函数编写必须记住的语法规则。
问题 4 C 语言 struct 函数使用 typedef 有什么好处
回答 1 C 语言 struct 函数搭配 typedef 可以给 struct 自定义别名,后续创建 struct 变量的时候不用重复书写 struct 关键字,简化 struct 函数相关代码书写,提升代码可读性。
【免责申明】
本文内容基于标准 C 语言语法编写,不同编译器对内存对齐的默认规则存在差异,代码示例仅用于学习演示,嵌入式平台部分编译器会提供修改对齐方式的扩展选项,实际项目开发请结合编译环境测试,因浅拷贝内存对齐理解错误造成程序异常,使用者自行承担相关风险。


