📋第九章知识体系总览(谭浩强 P.223-279)
- 9.1 typedef — 类型重定义
- 9.2 结构体类型
- 9.3 结构体数组
- 9.4 结构体指针
- 9.5 用指针处理链表
- 9.6 共用体类型
- 9.7 枚举类型
- 9.8 类型定义符typedef
💡 本章重点:结构体是C语言中组织复杂数据的核心工具。掌握结构体的定义、成员访问、嵌套以及链表操作是开发复杂程序的基础。
🗺️本章学习路线图
① typedef
类型重定义
类型重定义
② 结构体
定义与使用
定义与使用
③ 结构体数组
批量数据
批量数据
④ 结构体指针
p->member
p->member
⑤ 链表
动态数据结构
动态数据结构
⑥ 共用体
内存复用
内存复用
📚谭浩强教材配套说明
教材章节对应:
📌 9.1 定义结构体类型(P.224-232):结构体声明,定义变量,引用成员
📌 9.2 结构体数组(P.233-243):数组与结构体结合,排序查找
📌 9.3 结构体指针(P.244-251):指向结构体的指针,p->member语法
📌 9.4 用指针处理链表(P.252-266):链表概念,创建/插入/删除/遍历
📌 9.5 共用体(P.267-273):共用体概念,与结构体区别
📌 9.6 枚举类型(P.274-279):枚举定义,使用优点
📌 9.7 用typedef定义类型(P.280-283):类型重定义,简化代码
📌 9.1 定义结构体类型(P.224-232):结构体声明,定义变量,引用成员
📌 9.2 结构体数组(P.233-243):数组与结构体结合,排序查找
📌 9.3 结构体指针(P.244-251):指向结构体的指针,p->member语法
📌 9.4 用指针处理链表(P.252-266):链表概念,创建/插入/删除/遍历
📌 9.5 共用体(P.267-273):共用体概念,与结构体区别
📌 9.6 枚举类型(P.274-279):枚举定义,使用优点
📌 9.7 用typedef定义类型(P.280-283):类型重定义,简化代码
🔤9.7 typedef — 类型重定义(谭浩强 P.280-283)
typedef 的作用:为已有类型名创建一个新的别名,使代码更简洁、更易读。
📝基本语法
// 原类型写法
struct Point {
int x;
int y;
};
struct Point p1; // 每次都要写struct
// typedef 简化后
typedef struct Point {
int x;
int y;
} Point; // Point 是类型名,不是变量名
Point p1; // 直接用 Point 定义变量
typedef int INTEGER; // int 的别名
INTEGER a = 10; // 等价于 int a = 10;
✅ 作用:简化冗长的类型名,提高代码可读性
⚖️typedef vs #define
| 对比项 | typedef | #define |
|---|---|---|
| 处理阶段 | 编译时处理 | 预处理阶段(文本替换) |
| 类型检查 | ✅ 有类型检查 | ❌ 无类型检查 |
| 复杂类型 | ✅ 可处理指针、数组、结构体 | ❌ 只能文本替换 |
| 示例 | typedef int* IP; | #define IP int* |
| IP a, b; | int *a, *b; | int *a, b;(b是int) |
💡实际应用场景
// 1. 简化结构体定义
typedef struct Student {
char name[20];
int age;
float score;
} Student;
// 2. 定义函数指针类型
typedef int (*CompareFunc)(void*, void*);
// 3. 简化复杂数组类型
typedef char NameArr[20];
NameArr names[100]; // char names[100][20]
// 4. 简化标准类型
typedef unsigned char BYTE;
typedef unsigned int UINT;
📦9.1 结构体基础(谭浩强 P.224-232)
结构体的特点:
📌 结构体是一种复合数据类型,可以将不同类型的数据组合成一个整体
📌 结构体的各个成员独立存储,内存布局连续
📌 与数组不同,结构体可以包含不同类型的成员
📌 结构体是一种复合数据类型,可以将不同类型的数据组合成一个整体
📌 结构体的各个成员独立存储,内存布局连续
📌 与数组不同,结构体可以包含不同类型的成员
🖊️定义结构体的三种方式
方式一:先定义类型,再定义变量(最常用)
struct Point { // 定义结构体类型
int x; // 成员1
int y; //成员2
}; // 注意:这里有分号!
struct Point p1, p2; // 定义结构体变量
方式二:定义类型的同时定义变量
struct Student {
char name[20];
int age;
float score;
} s1, s2; // 定义变量
方式三:直接定义结构体变量(匿名结构体)
struct {
int width;
int height;
} rect; // rect是变量,不是类型名
💻内存模型可视化
选择字段高亮:
💡 struct Student 占用 28 字节 = 20(name) + 4(age) + 4(score)
🔧引用结构体成员
// 定义结构体
struct Student {
char name[20];
int age;
float score;
};
// 定义变量
struct Student stu;
// 赋值(使用 . 运算符)
stu.age = 18;
stu.score = 95.5;
// 注意:字符数组不能用=直接赋值,要用strcpy
strcpy(stu.name, "张三");
// 读取
printf("%s %d %.1f", stu.name, stu.age, stu.score);
点击"演示成员访问"观察结构体成员的赋值与读取...
🔗结构体的嵌套
// 日期结构体
struct Date {
int year;
int month;
int day;
};
// 人员结构体(嵌套Date)
struct Person {
char name[30];
struct Date birthday; // 嵌套结构体
char gender;
};
struct Person p;
p.birthday.year = 2000; // 访问嵌套成员
p.birthday.month = 1;
p.birthday.day = 1;
⚠️ 注意:结构体不能嵌套自身,但可以嵌套指向自身的指针(链表的基础)
📊9.2 结构体数组(谭浩强 P.233-243)
结构体数组:将结构体类型与数组结合,存储多个结构体变量的数据。
// 定义结构体数组
struct Student {
char name[20];
int age;
float score;
};
struct Student class[5]; // 5个学生的数组
// 赋值
class[0].age = 18;
class[0].score = 92.5;
strcpy(class[0].name, "张三");
👥学生信息管理演示
| 序号 | 姓名 | 年龄 | 成绩 |
|---|
点击按钮操作学生数组...
📝内存布局
struct Student { char name[20]; int age; float score; }
每个学生占 28 字节,数组连续存储:
每个学生占 28 字节,数组连续存储:
💡 内存布局:每个元素28字节 = 20(name) + 4(age) + 4(score),数组元素连续排列
🔗9.3 结构体指针(谭浩强 P.244-251)
结构体指针:指向结构体变量的指针,通过指针访问成员有两种方式:
📌
📌
📌
p->member (箭头运算符)📌
(*p).member (先解引用,再点运算符)
➡️箭头运算符 p->member
struct Student {
char name[20];
int age;
};
struct Student stu = {"李四", 20};
struct Student *p = &stu; // 定义指向stu的指针
// 两种等价写法
p->age; // ✅ 箭头运算符(常用)
(*p).age; // ✅ 先解引用再点运算符
stu.age; // ✅ 直接访问
点击按钮观察三种访问方式的结果...
🔄指针遍历结构体数组
struct Student class[3] = {
{"甲", 18},
{"乙", 19},
{"丙", 20}
};
// 用指针遍历数组
for (struct Student *p = class; p < class + 3; p++) {
printf("%s %d\n", p->name, p->age);
}
📌p->member 与 (*p).member 的区别
| 表达式 | 含义 | 注意 |
|---|---|---|
| p->member | 通过指针访问成员 | 简洁,推荐使用 |
| (*p).member | 解引用后用点运算符 | 括号不可省略! |
| p.member | 错误! | p是指针,不是结构体 |
🔗🔗9.4 用指针处理链表(谭浩强 P.252-266)
链表的特点:
📌 链表是一种动态数据结构,可以动态地分配和释放内存
📌 链表节点包含数据域和指针域
📌 通过指针将各节点连接起来,不需要连续的内存空间
📌 插入/删除操作高效,不需要移动大量元素
📌 链表是一种动态数据结构,可以动态地分配和释放内存
📌 链表节点包含数据域和指针域
📌 通过指针将各节点连接起来,不需要连续的内存空间
📌 插入/删除操作高效,不需要移动大量元素
🏗️链表节点结构
// 定义链表节点结构体
typedef struct Node {
int data; // 数据域:存储数据
struct Node *next; // 指针域:指向下一个节点
} Node;
💡 节点结构:data(数据)+ next(指针)
最后一个节点的 next = NULL 表示链表结束
最后一个节点的 next = NULL 表示链表结束
🎬链表操作演示
点击按钮操作链表...
📝创建链表代码
// 创建一个包含3个节点的链表
Node* createList() {
Node *head, *p1, *p2;
p1 = p2 = (Node*)malloc(sizeof(Node));
p1->data = 10; p1->next = NULL;
head = p1;
p2 = (Node*)malloc(sizeof(Node));
p2->data = 20; p2->next = NULL;
p1->next = p2;
return head; // 返回头指针
}
⚠️ 重要:链表操作必须使用
malloc() 动态分配内存,free() 释放内存
⚡链表 vs 数组
| 对比项 | 链表 | 数组 |
|---|---|---|
| 存储方式 | 不连续,通过指针连接 | 连续内存 |
| 插入/删除 | ✅ O(1),只需修改指针 | ❌ O(n),需移动元素 |
| 访问元素 | ❌ O(n),需遍历 | ✅ O(1),直接下标访问 |
| 内存使用 | 按需分配,有额外指针开销 | 固定大小 |
🔀9.5 共用体(谭浩强 P.267-273)
共用体(Union)的特点:
📌 多个成员共享同一段内存
📌 任何时刻只能存储一个成员的值
📌 共用体的大小 = 最大成员的大小
📌 用于节省内存或实现数据类型转换
📌 多个成员共享同一段内存
📌 任何时刻只能存储一个成员的值
📌 共用体的大小 = 最大成员的大小
📌 用于节省内存或实现数据类型转换
📝共用体定义
// 共用体定义
union Data {
int i; // 占用4字节
float f; // 占用4字节
char ch; // 占用1字节
};
union Data d;
d.i = 65; // 存储int,共享内存
printf("%c", d.ch); // 输出 'A'(65的ASCII码)
⚖️结构体 vs 共用体
💡 结论:结构体各成员独立存储,共用体各成员共享同一内存
💡共用体的应用场景
// 1. 节省内存:一个人可能是学生或教师
struct Person {
char name[20];
int type; // 0=学生, 1=教师
union {
int studentId;
char teacherId[10];
} info;
};
// 2. 数据类型转换
union Converter {
float f;
unsigned int bits; // 查看float的二进制位
};
📝9.6 枚举类型(谭浩强 P.274-279)
枚举(Enum)的特点:
📌 枚举将整型常量组织成有意义的名字
📌 枚举常量默认从0开始递增
📌 提高代码可读性和可维护性
📌 编译时检查,减少魔法数字
📌 枚举将整型常量组织成有意义的名字
📌 枚举常量默认从0开始递增
📌 提高代码可读性和可维护性
📌 编译时检查,减少魔法数字
📝枚举定义
// 定义枚举类型(谭浩强 P.274)
enum Weekday {
SUNDAY, // 0
MONDAY, // 1
TUESDAY, // 2
WEDNESDAY, // 3
THURSDAY, // 4
FRIDAY, // 5
SATURDAY // 6
};
// 可以指定初始值
enum Color {
RED = 1,
GREEN = 2,
BLUE = 4
};
// 使用枚举
enum Weekday today = MONDAY;
printf("%d", today); // 输出 1
SUNDAY(0)
MONDAY(1)
TUESDAY(2)
WEDNESDAY(3)
THURSDAY(4)
FRIDAY(5)
SATURDAY(6)
点击选择星期,当前选中:MONDAY = 1
💡枚举的优点
为什么使用枚举?
📌 可读性好:MONDAY 比数字 1 更易理解
📌 易维护:修改枚举值,所有引用自动更新
📌 类型安全:编译器检查类型匹配
📌 调试方便:调试器显示符号名而非数字
📌 可读性好:MONDAY 比数字 1 更易理解
📌 易维护:修改枚举值,所有引用自动更新
📌 类型安全:编译器检查类型匹配
📌 调试方便:调试器显示符号名而非数字
// 不推荐:魔法数字
if (status == 1) { ... } // status是什么?
// 推荐:枚举
enum Status { IDLE=1, RUNNING, PAUSED, STOPPED };
if (status == RUNNING) { ... } // 一目了然