16-C语言综合项目

plana_lab 发布于 12 天前 83 次阅读



前面我们已经学习了:

变量和数据类型
输入输出
运算符
判断
循环
函数
数组
...

到这里,真正缺少的不是新的语法,而是:

把这些知识组合起来。

所以这一章不再以“学习一个新语法”为主,而是通过几个实际项目:

需求
↓
设计数据
↓
写函数
↓
拆分模块
↓
保存文件
↓
编译
↓
调试

完成一个真正可以运行的 C 程序。


一、本章项目路线

建议按照难度逐渐增加:

项目 1:猜数字
        ↓
项目 2:学生管理系统
        ↓
项目 3:课程表管理系统

三个项目分别锻炼:

猜数字
↓
基础语法 + 函数 + 循环

学生管理系统
↓
数组 + 字符串 + 结构体 + 文件

课程表管理系统
↓
结构体 + 动态内存 + 文件
+ 多文件 + Makefile
+ 完整项目设计

二、项目 1:猜数字

这是最简单的综合项目。

项目需求

程序随机生成一个数字:

1 ~ 100

然后用户不断输入:

你猜一个数字

程序告诉用户:

太大
太小
猜对了

例如:

请输入数字:50
太小

请输入数字:75
太大

请输入数字:63
猜对了!

三、猜数字需要哪些知识

变量
↓
scanf
↓
if
↓
while
↓
函数
↓
随机数

可以进一步练习:

统计猜了多少次

四、猜数字基础代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {

    srand((unsigned int)time(NULL));

    int target = rand() % 100 + 1;
    int guess;
    int count = 0;

    while (1) {

        printf("请输入数字: ");
        scanf("%d", &guess);

        count++;

        if (guess > target) {
            printf("太大了\n");
        }
        else if (guess < target) {
            printf("太小了\n");
        }
        else {
            printf("猜对了!\n");
            printf("你一共猜了 %d 次\n", count);
            break;
        }
    }

    return 0;
}

五、猜数字升级

可以加入:

难度
├─ 简单:1 ~ 50
├─ 普通:1 ~ 100
└─ 困难:1 ~ 1000

还可以:

限制猜测次数

例如:

最多 10 次

也可以增加:

重新开始
退出

这就开始有“小程序”的感觉了。


六、项目 2:学生管理系统

这是本章第一个真正重要的项目。

项目目标:

添加学生
删除学生
修改学生
查询学生
显示所有学生
保存学生
读取学生
退出程序

七、学生的数据结构

前面已经学过结构体。

可以定义:

typedef struct {
    int id;
    char name[50];
    int age;
    double score;
} Student;

一个学生:

Student
├─ id
├─ name
├─ age
└─ score

八、学生管理系统的数据

最简单版本可以:

Student students[100];

但是:

最多只能 100 人

为了练习动态内存,可以进一步:

Student *students;

然后:

students = malloc(sizeof(Student) * capacity);

这样就可以在运行时决定容量。


九、学生管理系统的功能

推荐设计:

1. 添加学生
2. 删除学生
3. 修改学生
4. 查询学生
5. 显示全部
6. 保存数据
7. 读取数据
0. 退出

十、菜单

程序启动:

========== 学生管理系统 ==========

1. 添加学生
2. 删除学生
3. 修改学生
4. 查询学生
5. 显示全部
6. 保存数据
7. 读取数据
0. 退出

请输入选择:

这里主要使用:

printf
scanf
switch
while

十一、添加学生

函数:

void add_student(Student *students, int *count);

基本逻辑:

判断是否还有空间
↓
输入 id
↓
输入 name
↓
输入 age
↓
输入 score
↓
count++

十二、为什么 count 要传指针

例如:

int count = 0;

函数:

void add_student(Student *students, int *count)

然后:

(*count)++;

因为我们要修改:

main 中的 count

这正好应用了之前学的:

指针
+
函数参数

十三、显示学生

void print_students(Student *students, int count)

使用:

for

遍历:

students[0]
students[1]
students[2]

输出:

ID    姓名    年龄    成绩
1     Alice   18      95.5
2     Bob     19      88.0

十四、查询学生

可以按照:

ID

查询。

例如:

Student *find_student(
    Student *students,
    int count,
    int id
);

找到后:

return &students[i];

找不到:

return NULL;

这又同时使用了:

结构体指针
+
函数返回值
+
NULL

十五、修改学生

先:

输入 ID

然后:

find_student()

找到:

Student *

然后修改:

student->name
student->age
student->score

十六、删除学生

删除一个数组元素:

找到目标
↓
后面的元素全部向前移动
↓
count--

例如:

A B C D

删除:

B

变成:

A C D

代码思想:

for (int i = index; i < count - 1; i++) {
    students[i] = students[i + 1];
}

这会再次用到:

结构体
+
数组
+
for

十七、保存文件

学生数据不能只存在内存。

程序退出以后:

students
↓
消失

所以需要:

保存
↓
students.dat

例如:

fwrite(
    students,
    sizeof(Student),
    count,
    fp
);

十八、读取文件

程序启动时:

students.dat
↓
fread
↓
students

于是:

上次的数据
↓
本次启动继续使用

这就是:

数据持久化。


十九、学生管理系统文件结构

到这里就不能再把所有代码全部堆在 main.c。

推荐:

student_manager/
│
├─ main.c
│
├─ student.c
├─ student.h
│
├─ file.c
├─ file.h
│
├─ config.h
│
└─ Makefile

二十、student.h

#ifndef STUDENT_H
#define STUDENT_H

typedef struct {
    int id;
    char name[50];
    int age;
    double score;
} Student;

void add_student(
    Student *students,
    int *count
);

void print_students(
    Student *students,
    int count
);

Student *find_student(
    Student *students,
    int count,
    int id
);

void delete_student(
    Student *students,
    int *count
);

void modify_student(
    Student *students,
    int count
);

#endif

这里放:

结构体
+
函数声明

二十一、file.h

#ifndef FILE_H
#define FILE_H

#include "student.h"

int save_students(
    const char *filename,
    Student *students,
    int count
);

int load_students(
    const char *filename,
    Student *students,
    int *count
);

#endif

二十二、main.c

main.c 主要负责:

菜单
流程控制
调用模块

例如:

#include <stdio.h>
#include "student.h"
#include "file.h"

int main() {

    Student students[100];
    int count = 0;

    while (1) {

        printf("\n");
        printf("1. 添加学生\n");
        printf("2. 删除学生\n");
        printf("3. 修改学生\n");
        printf("4. 查询学生\n");
        printf("5. 显示全部\n");
        printf("6. 保存\n");
        printf("7. 读取\n");
        printf("0. 退出\n");

        int choice;
        scanf("%d", &choice);

        switch (choice) {

            case 1:
                add_student(students, &count);
                break;

            case 2:
                delete_student(students, &count);
                break;

            case 3:
                modify_student(students, count);
                break;

            case 5:
                print_students(students, count);
                break;

            case 6:
                save_students(
                    "students.dat",
                    students,
                    count
                );
                break;

            case 7:
                load_students(
                    "students.dat",
                    students,
                    &count
                );
                break;

            case 0:
                return 0;
        }
    }
}

这里没有实现具体功能。

这正是多文件编程的意义:

main.c
↓
负责“调用谁”

而:

student.c
↓
负责“学生功能怎么实现”

二十三、学生管理系统升级:动态内存

把:

Student students[100];

升级为:

Student *students;

然后:

int capacity = 10;
int count = 0;

students = malloc(
    sizeof(Student) * capacity
);

如果满了:

count == capacity
↓
realloc
↓
扩大空间

例如:

Student *temp = realloc(
    students,
    sizeof(Student) * capacity * 2
);

if (temp != NULL) {
    students = temp;
    capacity *= 2;
}

这样就形成了:

结构体
+
指针
+
malloc
+
realloc
+
free

二十四、项目 3:课程表管理系统

这个项目非常适合拿来作为最终项目。

因为你之前已经实际做过课程表。

目标:

课程添加
课程删除
课程修改
查询课程
显示课程表
保存课程表
读取课程表

二十五、课程数据结构

可以设计:

typedef struct {
    char name[100];
    char teacher[100];
    char room[100];
    int day;
    int period;
} Course;

代表:

课程名称
教师
教室
星期
第几节

例如:

C语言
张老师
A101
星期一
第1节

二十六、课程表的数据

如果固定:

7 天
6 节

可以:

Course courses[7][6];

但是如果课程数量不固定:

Course *courses;

再配合:

malloc
realloc
free

就可以做动态课程列表。


二十七、课程表菜单

========== 课程表管理 =========

1. 添加课程
2. 删除课程
3. 修改课程
4. 查询课程
5. 显示课程表
6. 保存课程表
7. 读取课程表
0. 退出

二十八、显示课程表

例如:

          第1节       第2节       第3节
星期一    C语言       数学         英语
星期二    物理        计算机       体育
星期三    ...

这里主要使用:

二维数组
for
结构体
字符串

二十九、课程冲突检查

这是一个很有意思的功能。

例如:

星期一
第1节

已经有:

C语言

那么再次添加:

数学
星期一
第1节

就应该提示:

课程时间冲突!

逻辑:

遍历现有课程
↓
比较 day
↓
比较 period
↓
相同?
↓
冲突

三十、课程表文件保存

可以使用二进制:

fwrite(
    courses,
    sizeof(Course),
    count,
    fp
);

也可以保存成文本。

例如:

C语言|张老师|A101|1|1
数学|李老师|B202|1|2

三十一、进一步:JSON

如果以后希望:

C 程序
↓
生成 JSON
↓
网页
↓
手机 App

那么可以设计:

{
    "name": "C语言",
    "teacher": "张老师",
    "room": "A101",
    "day": 1,
    "period": 1
}

不过:

JSON 并不是 C 标准库自带的文件格式。

需要自己写 JSON 生成/解析代码,或者使用第三方 JSON 库。

当前基础课程不必马上深入。


三十二、最终项目架构

如果把课程表做到比较完整,可以:

course_manager/
│
├─ main.c
│
├─ course.c
├─ course.h
│
├─ file.c
├─ file.h
│
├─ config.h
│
├─ Makefile
│
└─ data/
   └─ courses.dat

三十三、course.h

#ifndef COURSE_H
#define COURSE_H

typedef struct {
    char name[100];
    char teacher[100];
    char room[100];
    int day;
    int period;
} Course;

void add_course(
    Course **courses,
    int *count,
    int *capacity
);

void delete_course(
    Course *courses,
    int *count
);

void modify_course(
    Course *courses,
    int count
);

void print_courses(
    Course *courses,
    int count
);

Course *find_course(
    Course *courses,
    int count,
    int day,
    int period
);

#endif

这里已经用到了:

二级指针

因为:

Course **courses

可能需要修改:

courses

本身指向的地址,例如 realloc() 后指针可能发生变化。

这正好是之前指针学习成果的最终应用之一。


三十四、文件模块

可以:

file.h
↓
save_courses()
load_courses()

file.c
↓
真正实现文件操作

这样主程序完全不用关心:

fopen
fread
fwrite
fclose

三十五、配置模块

例如:

#ifndef CONFIG_H
#define CONFIG_H

#define DEFAULT_CAPACITY 16
#define COURSE_NAME_SIZE 100
#define TEACHER_NAME_SIZE 100
#define ROOM_NAME_SIZE 100

#endif

这样修改配置的时候:

只改 config.h

三十六、Makefile

最终项目就可以使用:

CC = gcc
CFLAGS = -Wall -Wextra -g

TARGET = course_manager

OBJS = main.o course.o file.o

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) $^ -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean

clean:
    rm -f $(TARGET) $(OBJS)

编译:

make

清理:

make clean

三十七、调试最终项目

项目越大,越容易产生 Bug。

建议:

普通问题
↓
printf

编译问题
↓
-Wall -Wextra

内存问题
↓
AddressSanitizer

崩溃
↓
GDB

Linux 内存检查
↓
Valgrind

例如:

gcc -Wall -Wextra -g \
    -fsanitize=address,undefined \
    main.c course.c file.c \
    -o course_manager

三十八、项目开发流程

以后不要一上来就写几千行代码。

推荐:

① 确定需求
      ↓
② 设计数据结构
      ↓
③ 设计函数
      ↓
④ 先实现最简单功能
      ↓
⑤ 测试
      ↓
⑥ 拆成 .h / .c
      ↓
⑦ 加入文件保存
      ↓
⑧ 加入动态内存
      ↓
⑨ 加入错误处理
      ↓
⑩ Makefile
      ↓
⑪ 调试

三十九、不要一次写完整项目

例如学生管理系统。

不要:

一天写完 2000 行

推荐:

第一阶段
↓
显示菜单

第二阶段
↓
添加学生

第三阶段
↓
显示学生

第四阶段
↓
查询

第五阶段
↓
修改 / 删除

第六阶段
↓
文件保存

第七阶段
↓
动态内存

第八阶段
↓
拆分多文件

每一步都先运行。


四十、测试

一个程序不是:

能运行

就算完成。

需要测试:

正常输入
↓
边界输入
↓
错误输入
↓
极端输入
↓
重复操作
↓
空数据
↓
文件不存在
↓
文件损坏

例如学生管理系统:

0 个学生
1 个学生
100 个学生
超过容量
重复 ID
不存在的 ID
空文件
不存在的文件

四十一、最终项目应该具备什么

一个合格的综合项目至少应该:

✓ 能正常运行
✓ 有清晰的数据结构
✓ 使用函数拆分功能
✓ 使用结构体组织数据
✓ 正确使用指针
✓ 正确管理动态内存
✓ 能保存数据
✓ 能读取数据
✓ 使用 .h / .c 模块化
✓ 使用 Makefile 构建
✓ 能处理基本错误
✓ 能调试

四十二、综合项目能力对应表

学习内容                    项目中的应用
──────────────────────────────────────
变量                        保存状态
printf / scanf              输入输出
if / switch                 菜单判断
for / while                 遍历和菜单循环
函数                        功能拆分
数组                        保存多个对象
字符串                      姓名、课程名等
指针                        修改数据、传递地址
二级指针                    realloc 等场景
结构体                      Student / Course
malloc                      动态数据
realloc                     扩容
free                        释放内存
文件操作                    数据持久化
#define                     配置
.h / .c                     模块化
extern / static             模块管理
Makefile                    自动构建
GDB                         调试
Sanitizer                   内存错误检查

四十三、本章推荐的三个项目难度

Level 1
└─ 猜数字

Level 2
└─ 学生管理系统
   ├─ 添加
   ├─ 删除
   ├─ 修改
   ├─ 查询
   ├─ 文件保存
   └─ 文件读取

Level 3
└─ 课程表管理系统
   ├─ 结构体
   ├─ 动态内存
   ├─ 时间冲突
   ├─ 文件持久化
   ├─ 多文件
   ├─ Makefile
   └─ 调试

四十四、最终练习:从零完成课程表系统

不要直接复制现成代码。

自己完成:

Step 1
设计 Course 结构体

Step 2
设计菜单

Step 3
添加课程

Step 4
显示课程

Step 5
查询课程

Step 6
修改课程

Step 7
删除课程

Step 8
检查时间冲突

Step 9
保存文件

Step 10
读取文件

Step 11
使用 malloc

Step 12
使用 realloc

Step 13
使用 free

Step 14
拆成 .h / .c

Step 15
写 Makefile

Step 16
使用 Sanitizer / GDB 调试

最终达到:

运行程序
↓
操作课程表
↓
关闭程序
↓
重新打开
↓
课程仍然存在

这才真正完成了:

一个完整的 C 语言项目。


四十五、完整课程结束

至此,这套 C 语言基础课程结束了

此作者没有提供个人介绍。
最后更新于 2026-09-24