🔥个人主页:代码不加冰(欢迎来访)
🎬作者简介: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个学生?噩梦。结构体就是用来把多个相关的数据打包成一个新的数据类型。
二、结构体(struct)
2.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字节 }; // 大小不是 1+4+2=7,而是 12 字节! struct B { char c; // 1字节 short s; // 2字节 int i; // 4字节 }; // 大小是 8 字节为什么?编译器为了CPU访问效率,会对结构体成员进行内存对齐:
每个成员的起始地址必须是其自身大小的整数倍
结构体总大小必须是最大成员大小的整数倍
实际应用:如果对内存敏感,可以将成员按大小从大到小排列,减少填充浪费。
三、共用体(union)
3.1 什么是共用体
共用体的所有成员共享同一块内存空间。同一时刻只能存储一个成员的值。
c
union 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) |
|---|---|---|
| 关键字 | struct | union |
| 内存模型 | 各成员独立存储 | 所有成员共享内存 |
| 总大小 | ≥各成员大小之和(有对齐填充) | =最大成员大小 |
| 成员关系 | 可同时使用所有成员 | 同一时刻只能用其中一个 |
| 典型用途 | 表示复合对象(学生、商品等) | 节省内存、多类型数据存储 |
| 修改影响 | 只修改被操作的成员 | 修改一个会影响其他所有成员 |
一句话总结:结构体是各占各的地,大家互不干扰;共用体是轮流住一间房,一次只能住一个人。理解这个本质区别,就能轻松应对考试和实际编程。