整个第一阶段其实在回答一个问题:怎么让程序处理越来越复杂的数据和逻辑。 每个模块都在上一个的基础上,多解决一个“不够用”的问题:
| 模块 | 解决了什么问题 | 核心工具 |
|---|---|---|
| 模块一 | 让程序能收数据、算数据、吐结果 | int double string cin cout |
| 模块二 | 光会算不够,还要会看情况办事 | if / else if / else、&& || ! |
| 模块三 | 一遍不够,要能重复干 | while、for、break / continue |
| 模块四 | 代码堆成一坨,要能拆开整理 | 函数、参数、返回值 |
| 模块五 | 一次只管一个数不够,要批量存 | 数组、vector、遍历 |
| 模块六 | 零散数据要打包,操作要封装 | struct、引用 & |
下面四段代码都有 bug,而且都是前面课程里专门强调过的经典错误。 先别急着往下看,每段都花半分钟想想:错在哪?会输出什么?
int a = 7, b = 2;
double result = a / b;
std::cout << result << std::endl; // 打印出 3,不是 3.5 问题:a 和 b 都是 int,
a / b 是整数除法,小数部分被直接丢掉,算出 3
之后才赋值给 double——赋值救不回已经丢掉的精度。
改法:写成 (double)a / b,让除法在小数世界里进行。
int x = 5;
if (x = 10) { // 注意这里
std::cout << "相等" << std::endl;
}
std::cout << x << std::endl; // x 居然变成 10 了 问题:if (x = 10) 是赋值不是比较——
它把 10 塞进 x,然后拿 10 当条件
(非 0 即为真),所以永远进 if,还顺手改坏了 x。
改法:比较要用 ==。写 if 条件时,
看到单个 = 就该警觉。
int scores[5] = {90, 85, 70, 60, 95};
for (int i = 0; i <= 5; i++) { // 注意条件
std::cout << scores[i] << std::endl;
} 问题:长度为 5 的数组,合法下标只有 0~4。
条件写 i <= 5,最后一轮 i 等于 5,
scores[5] 越界——读到的是垃圾数据,甚至可能崩溃。
改法:遍历容器,条件永远用 i < 长度,不是 <=。
void addBonus(std::vector<int> scores) {
for (int i = 0; i < scores.size(); i++) {
scores[i] += 5;
}
}
// 调用后,外面的分数一分没涨 问题:参数是传值,函数拿到的是一份拷贝, 加的 5 分全加在拷贝上,函数一结束就没了。
改法:写成 std::vector<int>& scores。
口诀还是那句:要改就加 &。
把上面四段代码分别补成能编译的完整程序,先原样运行一遍, 亲眼看看错误的输出,再动手改对,对比前后差异。
亲手见过一次错误的输出,比读十遍文字说明记得牢。
下面这个小程序不长,但把六个模块的东西全用上了—— 读的时候留意每一处对应哪一讲:
#include <iostream>
#include <string>
#include <vector>
struct Student {
std::string name;
int score;
};
// 判断一个分数属于哪个等级
char gradeOf(int score) {
if (score >= 90) return 'A';
else if (score >= 80) return 'B';
else if (score >= 70) return 'C';
else if (score >= 60) return 'D';
else return 'E';
}
// 打印全班,附带等级
void printAll(const std::vector<Student>& students) {
for (int i = 0; i < students.size(); i++) {
std::cout << students[i].name
<< "\t" << students[i].score
<< "\t" << gradeOf(students[i].score) << std::endl;
}
}
int main() {
std::vector<Student> students = {
{"小明", 92}, {"小红", 85}, {"小刚", 58}
};
printAll(students);
return 0;
} | 代码里的东西 | 来自 |
|---|---|
| struct Student | 第 19 讲 |
| gradeOf 有返回值 | 第 12 讲 |
| if / else if 分档 | 第 6、7 讲 |
| for 遍历容器 | 第 15 讲 |
| vector<Student> | 第 16、19 讲 |
| const … & 参数 | 第 20 讲 |
"\t" 是制表符,作用是让输出对齐成列,
比敲很多空格整齐。它和 std::endl(换行)一样,是控制输出格式的小工具。
给 review_all.cpp 加一个函数 countGrade,
统计某个等级(比如 'A')一共有几个人,在 main 里调用并打印。
提示:函数需要两个参数——学生容器和要统计的等级字符;
循环里调用已有的 gradeOf 来判断。
写一个函数,打印所有不及格(低于 60 分)学生的姓名。 如果全班都及格,打印一句“全班及格”。
提示:用一个计数器或标记变量,记录到底有没有打印过人名——
这和第 10 讲猜数字游戏里 win 标记是同一个思路。
让用户输入一个姓名,在 vector 里查找这个学生:
找到就打印他的分数和等级,没找到就提示“查无此人”。
std::string 是否相同,可以直接用 ==。
找到之后还要不要继续找下去?回忆第 9 讲的 break。
把学生按分数从高到低排序后打印。
思路:最容易理解的办法是选择排序—— 先在全部学生里找出最高分的那个,放到第 1 位;再在剩下的里找最高分, 放到第 2 位……重复到结束。这需要两层嵌套循环, 外层控制“现在要确定第几名”,内层负责“在剩下的里面找最大”。
接着往下学之前,确认这些你都能不看笔记写出来:
cin 读入、cout 输出if / else if 做多档判断for 遍历一个 vectorstruct 并访问它的成员vector 传给函数时,知道什么时候该加 &有卡壳的地方,回到对应那一讲重看一遍再往下走—— 接下来两节要把这些同时用上。