← 课程目录
第 22 讲 模块六:进阶与综合

综合项目(一):学生成绩管理

22 / 24
💡
本节课目标
用两节课做完第一阶段的压轴项目——学生成绩管理系统。 今天先搭骨架:设计数据结构写出能反复操作的菜单循环、 实现添加显示全部两个功能,跑出一个真能用的程序。
开场

这次做一个“完整的软件”

📖 理论讲解

和前面的实战有什么不一样

前面每次实战,程序都是从头跑到尾就结束:算一次、猜一局、统计一遍。 这次不一样——它会一直等着你下命令,你想添加就添加、想查看就查看, 直到你主动选择退出。这就是绝大多数真实软件的工作方式。

📖 理论讲解

最终效果预览

运行效果 TEXT
===== 学生成绩管理系统 =====
1. 添加学生
2. 显示全部
3. 查找学生
4. 删除学生
5. 成绩统计
0. 退出
请选择:

两节课后,这六个功能会全部可用。今天先完成 1、2、0 三项, 其余的下节课补齐。

第一步

设计:先想清楚数据放在哪

📖 理论讲解

写代码之前,先定数据结构

做稍大的程序,第一件事不是急着敲代码,而是想清楚: 这个程序要管理什么数据?用什么装? 想明白这个,后面的功能全都围着它转。

data_design.cpp CPP
struct Student {
    std::string name;   // 姓名
    int score;          // 分数
};

// 全班学生都装在这里,长度随添加而增长
std::vector<Student> students;
为什么是 vector<Student>
学生人数不确定(随时可以添加)——所以用 vector 不用数组; 姓名和分数属于同一个人——所以用 struct 打包, 不用两个平行容器。这两个决定,正是第 16 讲和第 19 讲的直接应用。
📖 理论讲解

功能清单

再列清楚要做哪些功能,每个功能对应一个函数:

功能函数本节完成?
显示菜单printMenu
添加学生addStudent
显示全部printAll
查找学生findStudent下节课
删除学生removeStudent下节课
成绩统计showStats下节课
第二步

搭菜单循环:程序的骨架

📖 理论讲解

一个不肯结束的 while

菜单的核心是一个 while (true) 死循环:每轮打印菜单、读取选择、 执行对应功能,然后回到循环开头再来一遍。 只有用户选了退出,才 break 跳出去。

menu_loop.cpp CPP
#include <iostream>

void printMenu() {
    std::cout << "===== 学生成绩管理系统 =====" << std::endl;
    std::cout << "1. 添加学生" << std::endl;
    std::cout << "2. 显示全部" << std::endl;
    std::cout << "0. 退出" << std::endl;
    std::cout << "请选择:";
}

int main() {
    while (true) {
        printMenu();

        int choice;
        std::cin >> choice;

        if (choice == 1) {
            std::cout << "(这里以后放添加功能)" << std::endl;
        } else if (choice == 2) {
            std::cout << "(这里以后放显示功能)" << std::endl;
        } else if (choice == 0) {
            std::cout << "再见!" << std::endl;
            break;
        } else {
            std::cout << "无效选项,请重新输入。" << std::endl;
        }
    }
    return 0;
}
💡
这次的死循环是故意的
第 8 讲把 while (true) 当成要避免的 bug 讲过。 但那是忘了写更新条件造成的意外死循环;这里的 while (true)有意为之—— 因为我们确实希望它一直转下去,并且已经安排好了 break 这个出口。 关键区别在于:有没有一条明确的退路。
先搭空架子,再填功能
注意现在两个功能分支里只是打印一句占位文字。这样做的好处是: 可以立刻运行、立刻验证菜单本身是对的—— 选 1 有反应、选 0 能退出、乱输有提示。骨架确认没问题,再往里填真功能, 出了错也知道是新填的部分的问题。
⚡ 动手实操 约 10 分钟

先把空菜单跑起来

menu_loop.cpp 完整敲出来运行,逐项确认:

  1. 输入 12,各自打印对应的占位文字,然后菜单再次出现
  2. 输入 9 这种没有的选项,提示无效,菜单再次出现。
  3. 输入 0,打印再见并真的退出程序。

这三条都通过,说明骨架是稳的。

第三步

实现添加功能

📖 理论讲解

填好一个 Student,塞进容器

添加功能要修改 students,所以参数必须用 引用 &——这里如果忘了写, 添加会“看起来成功”但数据一个都存不下来:

add_student.cpp CPP
void addStudent(std::vector<Student>& students) {
    Student s;

    std::cout << "请输入姓名:";
    std::cin >> s.name;
    std::cout << "请输入分数:";
    std::cin >> s.score;

    students.push_back(s);
    std::cout << "添加成功!当前共 " << students.size() << " 名学生。" << std::endl;
}
⚠️
漏掉 & 是本项目最容易犯的错
第 20 讲专门强调过这个坑。如果添加完再选“显示全部”却什么都没有, 第一个要检查的就是这里的 &
第四步

实现显示全部

📖 理论讲解

别忘了“一个都没有”的情况

显示功能只数据,所以用 const &。 但要多考虑一步:如果一个学生都还没添加,直接打印表头会很奇怪—— 应该给一句友好的提示,然后提前 return

print_all.cpp CPP
void printAll(const std::vector<Student>& students) {
    if (students.empty()) {
        std::cout << "还没有任何学生记录。" << std::endl;
        return;
    }

    std::cout << "姓名\t分数" << std::endl;
    std::cout << "----------------" << std::endl;
    for (int i = 0; i < students.size(); i++) {
        std::cout << students[i].name << "\t" << students[i].score << std::endl;
    }
    std::cout << "共 " << students.size() << " 名学生。" << std::endl;
}
💡
students.empty() 是什么
vector 自带的一个小工具,容器为空时返回 true。 写 students.size() == 0 效果完全一样,但 .empty() 读起来更直白。
提前 return,又见老朋友
第 12 讲学过的提前 return:先处理掉特殊情况,早早退出, 后面的正常逻辑就不用再操心这种意外了。这是让函数保持清爽的常用手法。
实现

拼起来:本节完整代码

student_manager_v1.cpp CPP
#include <iostream>
#include <string>
#include <vector>

struct Student {
    std::string name;
    int score;
};

// ── 界面 ──────────────────────────────
void printMenu() {
    std::cout << std::endl;
    std::cout << "===== 学生成绩管理系统 =====" << std::endl;
    std::cout << "1. 添加学生" << std::endl;
    std::cout << "2. 显示全部" << std::endl;
    std::cout << "0. 退出" << std::endl;
    std::cout << "请选择:";
}

// ── 功能 ──────────────────────────────
void addStudent(std::vector<Student>& students) {
    Student s;
    std::cout << "请输入姓名:";
    std::cin >> s.name;
    std::cout << "请输入分数:";
    std::cin >> s.score;
    students.push_back(s);
    std::cout << "添加成功!当前共 " << students.size() << " 名学生。" << std::endl;
}

void printAll(const std::vector<Student>& students) {
    if (students.empty()) {
        std::cout << "还没有任何学生记录。" << std::endl;
        return;
    }
    std::cout << "姓名\t分数" << std::endl;
    std::cout << "----------------" << std::endl;
    for (int i = 0; i < students.size(); i++) {
        std::cout << students[i].name << "\t" << students[i].score << std::endl;
    }
    std::cout << "共 " << students.size() << " 名学生。" << std::endl;
}

// ── 主流程 ────────────────────────────
int main() {
    std::vector<Student> students;

    while (true) {
        printMenu();

        int choice;
        std::cin >> choice;

        if (choice == 1) {
            addStudent(students);
        } else if (choice == 2) {
            printAll(students);
        } else if (choice == 0) {
            std::cout << "再见!" << std::endl;
            break;
        } else {
            std::cout << "无效选项,请重新输入。" << std::endl;
        }
    }

    return 0;
}
留意这个结构

代码分成了三段:界面(printMenu)、功能 (addStudent、printAll)、主流程(main)。 main 里没有一行具体业务逻辑,只负责把用户的选择转交给对应的函数

这样组织的好处,下节课会非常明显:新增三个功能,只要写三个新函数, 再在 main 的分支里各加一行——已经写好的部分完全不用动

测试

验证今天的成果

⚡ 动手实操 约 15 分钟

按剧本走一遍

把完整代码跑起来,严格按下面的顺序操作,确认每步的表现:

操作期望表现
一上来就选 2提示还没有任何学生记录
选 1,添加“小明 90”提示添加成功,当前共 1 名
选 2表格里出现小明 90
再选 1,添加“小红 85”提示当前共 2 名
选 2两个人都在,共 2 名
选 7提示无效选项
选 0打印再见并退出
⚠️
最关键的一步
“添加两个人后,选 2 能看到两个人”——这一条如果不通过, 八成是 addStudent 的参数漏了 &
拓展

有余力可以先做

练习 22-1

添加时校验分数

addStudent 里加上校验:分数必须在 0~100 之间, 否则提示重新输入,直到合法为止。

提示:第 8 讲的输入校验 while 循环,原样搬过来。

练习 22-2

显示时带上序号

printAll 在每行前面加上序号(1、2、3……),方便用户指认。

提示:下标 i 从 0 开始,序号打印 i + 1

练习 22-3

顺便显示等级

把第 21 讲写过的 gradeOf 函数搬进这个项目, 让 printAll 多输出一列等级

收尾

小结

📖 理论讲解

今天完成了什么

做的事用到的知识
设计数据结构struct + vector
搭菜单循环while (true) + break
分发用户选择if / else if 多分支
添加学生引用参数 & + push_back
显示全部const & + 遍历 + 提前 return

更重要的是这个做项目的顺序:先定数据 → 再搭骨架 → 验证骨架 → 逐个填功能,每填一个就验证一次。

💡
下节课预告
下节课补齐查找、删除、统计三个功能。 查找要遍历比对姓名,删除要学一个新的 vector 用法, 统计则是把第 18 讲的总分、平均分、最高分搬进来。最后整体联调,修掉遗留 bug。