第二阶段第8周

容器(vector/map)与数据组织

这一周你会从“单个变量处理”走向“批量数据管理”。掌握 `vector` 和 `map` 后,你的程序就能应对更真实的数据场景。

学完这周,你能用容器组织、遍历和查询数据,并完成一个容器驱动的小项目。

C++ 第二阶段第8周课程封面

8.1 vector:顺序数据容器

`vector` 适合存放一组同类型数据,比如分数列表或任务列表。你可以动态添加、遍历统计,写法直接清晰。

vector<int> scores;
scores.push_back(88);
scores.push_back(92);
scores.push_back(75);

for (int s : scores) {
    cout << s << " ";
}

8.2 map:键值映射容器

`map` 适合“通过键找值”,比如姓名 -> 分数、商品 -> 价格。比数组下标更接近真实业务表达。

map<string, int> scoreMap;
scoreMap["Mia"] = 95;
scoreMap["Leo"] = 88;

cout << "Mia 的分数: " << scoreMap["Mia"] << "\n";

8.3 遍历与统计

容器的核心价值是批量处理。统计之前先判断容器是否为空,避免无意义计算或访问异常。

double calcAverage(const vector<int>& v) {
    if (v.empty()) return 0.0;
    long long sum = 0;
    for (int x : v) sum += x;
    return static_cast<double>(sum) / v.size();
}

8.4 深度讲解:为什么容器是“项目化能力”的门槛

前几周你主要在处理单个变量,本周开始要处理一组数据。项目里真正的难点往往不是某个公式,而是“数据如何组织”。如果数据组织混乱,后续功能扩展会越来越痛苦。容器就是解决这个问题的核心工具:`vector` 让你有序管理同类数据,`map` 让你通过键快速定位值。学会容器后,你会从“写题思维”迈向“系统思维”。

很多同学第一次用容器时会把它当“高级数组”,只会 `push_back` 和下标访问。实际上更重要的是思考:你的业务更适合顺序结构还是键值结构?例如“按录入顺序展示成绩”适合 `vector`;“按姓名查分”适合 `map`。容器选择本质上是数据模型设计。模型对了,后面的功能会自然;模型错了,后面每一步都别扭。

本周建议你把所有练习都先画数据结构草图:有哪些实体?每个实体有哪些字段?字段之间如何关联?哪部分用 `vector`,哪部分用 `map`?你会发现,建模一旦清楚,代码几乎是“顺着写出来”的。反过来,模型不清楚时,你会不停返工。第8周最宝贵的不是记住多少 API,而是建立“先设计数据,再写逻辑”的习惯。

8.5 vector 进阶:遍历、修改、删除的安全写法

`vector` 使用中最常见 bug 是“遍历时修改容器导致索引错乱”。例如你在 `for` 循环里按下标删除元素,后面的元素会前移,如果不调整索引就会跳过元素。学习阶段可先采用简单策略:先记录要删除的目标,再统一删除;或者从后往前删,减少位移影响。不要为了“写短代码”牺牲稳定性。

另一个问题是越界访问。`scores[5]` 只有在你确认 `size() > 5` 时才安全。建议你在处理外部输入索引时始终做边界判断,例如 `if (idx >= 0 && idx < scores.size())`。这类判断看起来啰嗦,但它是线上稳定性的底线。很多程序崩溃不是算法错,而是边界没守住。

遍历写法上,范围 `for` 对初学者更友好;需要索引时再用传统 `for (size_t i = 0; i < v.size(); i++)`。关键是选择最容易读懂的写法,而不是追求“最炫写法”。可读性高的代码,才更容易被未来的你维护。

vector<int> scores = {88, 92, 75, 60, 47};
for (size_t i = 0; i < scores.size(); i++) {
    if (scores[i] < 60) {
        cout << "待提升分数: index=" << i << ", value=" << scores[i] << "\n";
    }
}

8.6 map 进阶:查询策略与默认值陷阱

`map` 新手常见误区是直接用 `scoreMap[name]` 查询。这样写虽然方便,但当键不存在时会自动创建一个默认值,可能悄悄污染数据。更稳妥的方法是先判断键是否存在,再读取值。你可以使用 `find` 做显式查询:找到再用,找不到就给出提示。这样逻辑更可控,数据也更干净。

在业务场景中,你还要考虑“同名键更新”与“首次插入”两个路径是否一致。例如更新学生分数时,是覆盖旧值还是拒绝重复?这其实是业务规则,不是语法问题。写程序前先定规则,再写容器操作,能避免后期行为争议。工程开发里,数据规则清晰比代码技巧更重要。

map<string, int> scoreMap;
scoreMap["Mia"] = 95;

string name = "Leo";
auto it = scoreMap.find(name);
if (it == scoreMap.end()) {
    cout << "未找到该学生: " << name << "\n";
} else {
    cout << name << " 的分数: " << it->second << "\n";
}

8.7 第二阶段收束:从语法到小系统

如果你回看第4周到第8周,会发现自己完成了一个重要跨越:从“会写单条语句”到“能管理一组数据并组织成流程”。这就是项目能力的基础。未来无论你学面向对象、算法还是工程化,核心都离不开这几个能力:类型意识、控制流意识、函数边界意识、数据结构意识。

建议你在本周做一次完整复盘:第一,列出自己最常犯的 3 个错误;第二,为每个错误写一条预防规则;第三,把规则贴到下一周代码模板最上面。这个动作会让你的学习从“经验型”变成“方法型”。长期看,方法型学习者的成长速度通常更快、更稳。

第二阶段结束时,不需要追求“写得像高手”,但要确保“写得稳定、看得清楚、改得动”。只要达到这个状态,你进入第三阶段项目实战时会非常有底气。

8.8 课堂问答与进阶练习

问:`vector` 和 `map` 都能存数据,为什么要区分? 因为它们解决的问题不同。`vector` 擅长按顺序管理和遍历,`map` 擅长按键快速定位。你可以把它们理解为两种思维:一个是“按位置找”,一个是“按名字找”。项目里经常要同时用两者,例如用 `vector` 保持展示顺序,用 `map` 做快速索引。

问:我该优先学 API 还是先学数据建模? 优先建模。API 可以查文档,建模能力必须靠训练。每次做题先问:我有哪些实体?实体之间是什么关系?数据以后要按顺序展示还是按键查询?把这三个问题回答清楚,容器选择通常就自然了。你会发现,真正影响代码质量的往往不是“不会某个函数”,而是“数据结构一开始就选错了”。

问:为什么我程序能运行,但越改越乱? 因为缺少“数据层”和“逻辑层”边界。建议你把容器操作封装成函数:`addStudent`、`findStudent`、`printAllStudents`。主流程只负责调度。这样后续新增功能时,改动范围可控,不会每次都碰全局代码。第8周你要建立的核心能力就是“让数据结构支撑功能迭代”。

进阶练习建议:实现“课堂积分榜系统”。要求: 第一,用 `vector` 存排行榜顺序,用 `map` 存姓名到积分映射; 第二,支持新增同学、更新积分、按姓名查询、按积分区间统计人数; 第三,处理异常输入(空姓名、负积分、重复姓名策略); 第四,输出一份“数据一致性检查结果”,确认 `vector` 与 `map` 中同学数量一致。 这个练习会让你真正体验“多容器协同”的工程场景。

如果你完成得比较快,可以继续挑战“持久化版本”:把积分数据写入文本文件,下次运行程序时自动加载。这样你会把第二阶段函数、控制流、容器三块能力完整串起来,为第三阶段项目实战打下非常扎实的基础。

8.9 本周练习与自测清单

本周练习

  1. 完成“学生成绩管理器 v1”(新增、查询、平均分统计)。
  2. 使用 `vector` 存成绩,使用 `map` 建姓名索引。
  3. 提交 8 组测试,至少 2 组为空容器场景。

自测清单

补充建议:做一次“数据结构替换实验”,把同一需求分别用单一容器和组合容器实现,对比复杂度与可维护性,你会更直观理解建模价值。

阶段收束:做完第8周,你已经具备从语法到数据组织的完整基础,可以平稳进入下一阶段的项目化开发。

返回 C++ 第二阶段