C语言结构体与共用体:一篇文章彻底搞懂

📅 2026/6/22 0:57:43
C语言结构体与共用体:一篇文章彻底搞懂
个人主页代码不加冰欢迎来访作者简介java后端学习者❄️个人专栏LeetCode刷题日记 苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言大家好我是代码不加冰今天还是主要复习C语言后天要考试感觉还是没底因为在此之前一点都没学过今天学到了结构体和共用体在这里总结一下吧。结构体struct和共用体union是C语言中最重要的构造类型。本文从内存布局到实际应用带你一次掌握这两个核心知识点。一、为什么需要结构体1.1 基本数据类型的局限C语言的基本数据类型int、float、char等只能表示单一数据。但现实世界中的实体往往是复合的——比如一个学生有学号、姓名、年龄、成绩等多个属性。c // 不用结构体散落的变量难以管理 int stu_id 1001; char stu_name[20] 张三; int stu_age 20; float stu_score 85.5; // 如果要表示100个学生噩梦。结构体就是用来把多个相关的数据打包成一个新的数据类型。二、结构体struct2.1 定义结构体类型c // 语法格式 struct 结构体名 { 数据类型 成员1; 数据类型 成员2; // ... 更多成员 }; // 示例定义学生类型 struct Student { int id; // 学号 char name[20]; // 姓名 int age; // 年龄 float score; // 成绩 }; // 注意这里的分号不能省略关键理解struct Student只是定义了一个类型蓝图此时并没有分配内存。就像建筑师画了图纸但还没盖房子。2.2 声明结构体变量c // 方式1先定义类型再声明变量 struct Student stu1; // 方式2定义类型的同时声明变量 struct Student { int id; char name[20]; int age; float score; } stu1, stu2; // 直接声明了两个变量 // 方式3省略结构体名匿名结构体不推荐 struct { int id; char name[20]; } stu1; // 无法再声明同类型的其他变量2.3 初始化结构体变量c // 方式1按顺序初始化 struct Student stu1 {1001, 张三, 20, 85.5}; // 方式2指定成员初始化C99更安全 struct Student stu2 { .id 1002, .name 李四, .score 90.0, .age 21 }; // 方式3先声明后赋值 struct Student stu3; stu3.id 1003; strcpy(stu3.name, 王五); // 字符串不能直接赋值要用strcpy stu3.age 22; stu3.score 88.0;2.4 访问结构体成员使用点运算符.c #include stdio.h #include string.h int main() { struct Student stu {1001, 张三, 20, 85.5}; // 访问和修改成员 printf(学号%d\n, stu.id); printf(姓名%s\n, stu.name); printf(年龄%d\n, stu.age); printf(成绩%.1f\n, stu.score); stu.score 90.0; // 修改成绩 printf(修改后成绩%.1f\n, stu.score); return 0; }2.5 结构体数组c struct Student class[30]; // 可以存储30个学生 // 初始化结构体数组 struct Student class[3] { {1001, 张三, 20, 85.5}, {1002, 李四, 21, 90.0}, {1003, 王五, 19, 78.5} }; // 遍历访问 for (int i 0; i 3; i) { printf(%s的成绩是%.1f\n, class[i].name, class[i].score); }2.6 结构体指针当结构体较大时传递指针比传递整个结构体更高效。c // 定义结构体指针 struct Student stu {1001, 张三, 20, 85.5}; struct Student *p stu; // 通过指针访问成员使用 - 运算符 printf(姓名%s\n, p-name); // 等价于 (*p).name printf(学号%d\n, p-id); // 等价于 (*p).id // 修改成员 p-score 95.0;记忆口诀结构变量用点.结构指针用箭头-。2.7 结构体作为函数参数c // 方式1传值复制整个结构体效率低 void printStudent(struct Student s) { printf(姓名%s成绩%.1f\n, s.name, s.score); } // 方式2传指针推荐效率高可修改原数据 void updateScore(struct Student *p, float new_score) { p-score new_score; } // 使用 int main() { struct Student stu {1001, 张三, 20, 85.5}; printStudent(stu); // 传值 updateScore(stu, 95.0); // 传指针直接修改原数据 return 0; }2.8 结构体的内存对齐重要考点c struct A { char c; // 1字节 int i; // 4字节 short s; // 2字节 }; // 大小不是 1427而是 12 字节 struct B { char c; // 1字节 short s; // 2字节 int i; // 4字节 }; // 大小是 8 字节为什么编译器为了CPU访问效率会对结构体成员进行内存对齐每个成员的起始地址必须是其自身大小的整数倍结构体总大小必须是最大成员大小的整数倍实际应用如果对内存敏感可以将成员按大小从大到小排列减少填充浪费。三、共用体union3.1 什么是共用体共用体的所有成员共享同一块内存空间。同一时刻只能存储一个成员的值。cunion Data { int i; // 4字节 float f; // 4字节 char str[20]; // 20字节 };// 大小 20字节取最大成员的大小3.2 定义和使用共用体c #include stdio.h union Data { int i; float f; char str[20]; }; int main() { union Data data; data.i 10; printf(data.i %d\n, data.i); data.f 3.14; // 覆盖了i的值 printf(data.f %.2f\n, data.f); printf(data.i %d\n, data.i); // 此时i的值已被破坏 // 同一时刻只能正确使用一个成员 return 0; }3.3 结构体 vs 共用体特性结构体struct共用体union内存分配各成员独立分配总大小≥各成员大小之和所有成员共享内存总大小最大成员大小存储内容可同时存储所有成员的值同一时刻只能存储一个成员的值用途表示具有多个属性的复合对象节省内存同一数据的不同解释方式安全性各成员互不影响一个成员改变会影响其他成员3.4 共用体的典型应用应用1节省内存c // 表示一个值可以是整数、浮点数或字符串 union Value { int int_val; float float_val; char *str_val; }; // 配合枚举使用标记当前存储的是哪种类型 enum ValueType { INT, FLOAT, STRING }; struct Variable { enum ValueType type; union Value value; };应用2数据解析同一块内存的不同解释c // 将4字节拆解为4个单独的字节 union IPAddress { unsigned int addr; // 32位IP地址 unsigned char bytes[4]; // 4个字节 }; int main() { union IPAddress ip; ip.addr 0xC0A80101; // 192.168.1.1 // 通过字节数组访问每个字节 printf(%d.%d.%d.%d\n, ip.bytes[3], ip.bytes[2], ip.bytes[1], ip.bytes[0]); // 输出192.168.1.1 return 0; }四、结构体嵌套与复杂应用4.1 结构体嵌套c // 日期结构体 struct Date { int year; int month; int day; }; // 学生结构体包含日期 struct Student { int id; char name[20]; struct Date birthday; // 嵌套结构体 float score; }; int main() { struct Student stu {1001, 张三, {2000, 5, 15}, 85.5}; // 访问嵌套成员 printf(出生日期%d年%d月%d日\n, stu.birthday.year, stu.birthday.month, stu.birthday.day); return 0; }4.2 结构体包含共用体c // 表示不同类型的图形 struct Shape { char type; // C表示圆R表示矩形 union { struct { float radius; } circle; struct { float width, height; } rectangle; } data; }; int main() { struct Shape s; s.type C; s.data.circle.radius 5.0; if (s.type C) { printf(圆的面积%.2f\n, 3.14 * s.data.circle.radius * s.data.circle.radius); } return 0; }4.3 typedef简化结构体声明c // 不用typedef struct Student { int id; char name[20]; }; struct Student stu1; // 每次都要写struct // 使用typedef typedef struct Student { int id; char name[20]; } Student; // 现在Student就是一个类型名了 Student stu1; // 不用写struct更简洁 Student stu2; // 甚至可以这样匿名结构体 typedef struct { int id; char name[20]; } Student; // 结构体本身没有名字只有类型别名五、综合实战学生成绩管理系统c #include stdio.h #include string.h #define MAX_STUDENTS 100 // 日期类型 typedef struct { int year; int month; int day; } Date; // 学生类型 typedef struct { int id; char name[20]; Date birthday; float scores[3]; // 三门课成绩 float avg; // 平均分 } Student; // 函数声明 void inputStudent(Student *s); void printStudent(Student s); float calcAverage(Student s); void sortByAvg(Student arr[], int n); int main() { Student students[MAX_STUDENTS]; int n 0; int choice; while (1) { printf(\n 学生成绩管理系统 \n); printf(1. 添加学生\n); printf(2. 显示所有学生\n); printf(3. 按平均分排序\n); printf(4. 退出\n); printf(请选择); scanf(%d, choice); switch (choice) { case 1: if (n MAX_STUDENTS) { inputStudent(students[n]); students[n].avg calcAverage(students[n]); n; printf(添加成功\n); } else { printf(学生已满\n); } break; case 2: for (int i 0; i n; i) { printStudent(students[i]); } break; case 3: sortByAvg(students, n); printf(排序完成\n); break; case 4: return 0; default: printf(无效选择\n); } } return 0; } void inputStudent(Student *s) { printf(请输入学号); scanf(%d, s-id); printf(请输入姓名); scanf(%s, s-name); printf(请输入出生日期年 月 日); scanf(%d %d %d, s-birthday.year, s-birthday.month, s-birthday.day); printf(请输入三门课成绩); for (int i 0; i 3; i) { scanf(%f, s-scores[i]); } } void printStudent(Student s) { printf(学号%d\t姓名%s\t生日%d-%d-%d\t成绩%.1f %.1f %.1f\t平均%.1f\n, s.id, s.name, s.birthday.year, s.birthday.month, s.birthday.day, s.scores[0], s.scores[1], s.scores[2], s.avg); } float calcAverage(Student s) { float sum 0; for (int i 0; i 3; i) { sum s.scores[i]; } return sum / 3; } void sortByAvg(Student arr[], int n) { // 冒泡排序 for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j].avg arr[j 1].avg) { Student temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }六、期末高频考点速记6.1 结构体相关考点要点结构体定义struct 名称 { 成员列表 };分号不能忘变量声明struct 名称 变量名;或配合typedef简化成员访问变量使用.运算符成员访问指针使用-运算符结构体大小受内存对齐影响不等于各成员大小之和结构体传参推荐传指针避免大结构体复制开销6.2 共用体相关考点要点定义union 名称 { 成员列表 };内存所有成员共享同一块内存大小最大成员限制同一时刻只能正确使用一个成员用途节省内存、数据类型解析6.3 常见陷阱c // ❌ 陷阱1字符串直接赋值 struct Student stu; stu.name 张三; // 错误字符串数组不能直接赋值 // ✅ 正确做法 strcpy(stu.name, 张三); // ❌ 陷阱2忘记struct关键字没用typedef时 Student stu; // 错误C语言中要写struct Student // ✅ 正确做法 struct Student stu; // ❌ 陷阱3共用体同时使用多个成员 union Data d; d.i 10; printf(%f, d.f); // 错误刚存了整数却当浮点数读 // ❌ 陷阱4结构体比较 struct Student s1, s2; if (s1 s2) { } // 错误不能直接比较结构体 // 需要逐个成员比较七、总结对比表对比项结构体struct共用体union关键字structunion内存模型各成员独立存储所有成员共享内存总大小≥各成员大小之和有对齐填充最大成员大小成员关系可同时使用所有成员同一时刻只能用其中一个典型用途表示复合对象学生、商品等节省内存、多类型数据存储修改影响只修改被操作的成员修改一个会影响其他所有成员一句话总结结构体是各占各的地大家互不干扰共用体是轮流住一间房一次只能住一个人。理解这个本质区别就能轻松应对考试和实际编程。