前面每次实战,程序都是从头跑到尾就结束:算一次、猜一局、统计一遍。 这次不一样——它会一直等着你下命令,你想添加就添加、想查看就查看, 直到你主动选择退出。这就是绝大多数真实软件的工作方式。
===== 学生成绩管理系统 =====
1. 添加学生
2. 显示全部
3. 查找学生
4. 删除学生
5. 成绩统计
0. 退出
请选择: 两节课后,这六个功能会全部可用。今天先完成 1、2、0 三项, 其余的下节课补齐。
做稍大的程序,第一件事不是急着敲代码,而是想清楚: 这个程序要管理什么数据?用什么装? 想明白这个,后面的功能全都围着它转。
struct Student {
std::string name; // 姓名
int score; // 分数
};
// 全班学生都装在这里,长度随添加而增长
std::vector<Student> students; vector 不用数组;
姓名和分数属于同一个人——所以用 struct 打包,
不用两个平行容器。这两个决定,正是第 16 讲和第 19 讲的直接应用。
再列清楚要做哪些功能,每个功能对应一个函数:
| 功能 | 函数 | 本节完成? |
|---|---|---|
| 显示菜单 | printMenu | 是 |
| 添加学生 | addStudent | 是 |
| 显示全部 | printAll | 是 |
| 查找学生 | findStudent | 下节课 |
| 删除学生 | removeStudent | 下节课 |
| 成绩统计 | showStats | 下节课 |
菜单的核心是一个 while (true) 死循环:每轮打印菜单、读取选择、
执行对应功能,然后回到循环开头再来一遍。
只有用户选了退出,才 break 跳出去。
#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;
} while (true) 当成要避免的 bug 讲过。
但那是忘了写更新条件造成的意外死循环;这里的
while (true) 是有意为之——
因为我们确实希望它一直转下去,并且已经安排好了 break 这个出口。
关键区别在于:有没有一条明确的退路。 把 menu_loop.cpp 完整敲出来运行,逐项确认:
1、2,各自打印对应的占位文字,然后菜单再次出现。9 这种没有的选项,提示无效,菜单再次出现。0,打印再见并真的退出程序。这三条都通过,说明骨架是稳的。
添加功能要修改 students,所以参数必须用
引用 &——这里如果忘了写,
添加会“看起来成功”但数据一个都存不下来:
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;
} &。
显示功能只读数据,所以用 const &。
但要多考虑一步:如果一个学生都还没添加,直接打印表头会很奇怪——
应该给一句友好的提示,然后提前 return:
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;
} vector 自带的一个小工具,容器为空时返回 true。
写 students.size() == 0 效果完全一样,但 .empty()
读起来更直白。
#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 的分支里各加一行——已经写好的部分完全不用动。
把完整代码跑起来,严格按下面的顺序操作,确认每步的表现:
| 操作 | 期望表现 |
|---|---|
| 一上来就选 2 | 提示还没有任何学生记录 |
| 选 1,添加“小明 90” | 提示添加成功,当前共 1 名 |
| 选 2 | 表格里出现小明 90 |
| 再选 1,添加“小红 85” | 提示当前共 2 名 |
| 选 2 | 两个人都在,共 2 名 |
| 选 7 | 提示无效选项 |
| 选 0 | 打印再见并退出 |
addStudent 的参数漏了 &。
在 addStudent 里加上校验:分数必须在 0~100 之间,
否则提示重新输入,直到合法为止。
提示:第 8 讲的输入校验 while 循环,原样搬过来。
让 printAll 在每行前面加上序号(1、2、3……),方便用户指认。
提示:下标 i 从 0 开始,序号打印 i + 1。
把第 21 讲写过的 gradeOf 函数搬进这个项目,
让 printAll 多输出一列等级。
| 做的事 | 用到的知识 |
|---|---|
| 设计数据结构 | struct + vector |
| 搭菜单循环 | while (true) + break |
| 分发用户选择 | if / else if 多分支 |
| 添加学生 | 引用参数 & + push_back |
| 显示全部 | const & + 遍历 + 提前 return |
更重要的是这个做项目的顺序:先定数据 → 再搭骨架 → 验证骨架 → 逐个填功能,每填一个就验证一次。
vector 用法,
统计则是把第 18 讲的总分、平均分、最高分搬进来。最后整体联调,修掉遗留 bug。