C++ practical course · lesson 13

C++ 数组、vector 与迭代器

一条记录可以放在变量里,一批记录需要容器。本课用 vector 建立真正可用的数据列表,并理解连续存储、下标与迭代器。

Easy explanation

vector 像一排能够自动扩建的储物柜。

普通数组的柜子数量在创建时固定,vector 能在需要时扩建,并记录自己当前有多少元素。我们可以从尾部加入、按位置读取,也可以遍历所有柜子。扩建可能把整排柜子搬到新位置,因此保存旧元素地址或迭代器时要格外小心。

容器只负责存放数据,程序仍要决定如何查找、修改和删除。处理列表时先明确元素类型、唯一标识和允许操作。例如学生记录用学号作为唯一标识,比用可能重复的姓名更可靠。

目标 1根据大小是否固定选择 array 或 vector
目标 2使用下标、范围循环与迭代器遍历
目标 3完成 vector 增删改查和统计
目标 4使用二维 vector 表示表格或网格
C++ 数组、vector 与迭代器通俗学习插图
连续容器让同类型数据按顺序排列,算法可以逐项处理。

Core concepts

先把四个核心知识点说清楚。

01

固定数组

array<int, 5> scores{};

大小编译时确定,适合数量永远固定的小集合,并提供 size 和范围循环。

02

动态序列

vector<Student> students;

元素数量运行时变化,push_back 添加,size 返回数量,empty 判断是否为空。

03

安全访问

values.at(index)

at 会检查范围,下标来自用户时优先使用。方括号更快但越界不会自动保护。

04

迭代器

for (auto it = v.begin(); it != v.end(); ++it)

迭代器表示容器中的位置,许多 STL 算法用一对迭代器描述处理范围。

values.push_back(42);

在尾部添加一个元素,容器大小自动增加。

for (const auto& item : items)

只读遍历对象时用 const 引用,避免复制。

v.erase(v.begin() + index);

按位置删除后,后面的元素会向前移动,旧下标随之变化。

vector<vector<int>> grid(rows, vector<int>(cols));

二维 vector 可以表示座位表、地图或成绩矩阵。

Complete examples

两个例子,把知识变成能运行的程序。

Example 1

例子一:学生成绩列表

每个 Student 同时保存学号、姓名和分数。查找函数返回指针,找到时指向 vector 中的对象,找不到返回 nullptr。

修改前先验证新分数,避免把非法数据写入对象。真实项目还要保证学号唯一。

添加、查找和修改
struct Student { int id; string name; int score; };

Student* findById(vector<Student>& students, int id)
{
    for (Student& student : students)
        if (student.id == id) return &student;
    return nullptr;
}

vector<Student> students{{101, "Mia", 88}, {102, "Tom", 76}};
students.push_back({103, "Lina", 95});
if (Student* found = findById(students, 102))
    found->score = 82;

for (const Student& s : students)
    cout << s.id << ' ' << s.name << ' ' << s.score << '\n';
101 Mia 88102 Tom 82103 Lina 95

Example 2

例子二:删除所有不及格成绩

remove_if 不会直接缩短 vector,而是把保留元素移动到前面并返回新的逻辑结尾。erase 再真正删除尾部区域。

谓词函数接收一个元素并返回是否删除。把条件写成 lambda 能让规则紧挨操作位置。

erase-remove 惯用法
students.erase(
    remove_if(students.begin(), students.end(),
        [](const Student& student)
        {
            return student.score < 60;
        }),
    students.end());

cout << "剩余人数:" << students.size() << '\n';
原有成绩:88 45 76 52 95删除所有低于 60 的记录剩余人数:3

Algorithm thinking

先决定“一个元素是什么”,再决定怎样处理整组。

好的容器设计从元素类型开始。若三个 vector 分别保存姓名、学号和成绩,很容易长度不同步;把三项组合成 Student,再使用 vector<Student>,一条记录始终保持完整。

顺序查找从头看到尾,最多检查 n 个元素,简单且适合小列表。删除中间元素会移动后续内容,因此循环删除时要小心下标变化。优先使用标准算法或正确更新迭代器。

  1. 1
    定义元素

    把属于同一条记录的数据放进 struct 或 class。

  2. 2
    确定唯一标识

    选择学号、编号等稳定键,不依赖可能重复或变化的显示名称。

  3. 3
    编写单项操作

    先完成查找,再在查找结果上实现修改和删除。

  4. 4
    测试容器变化

    覆盖空列表、一个元素、首尾元素和找不到目标。

Common mistakes

这些错误很常见,学会自己排查。

1

下标越界

合法下标最大是 size()-1,空 vector 时不能计算最后下标。

2

遍历时错误删除

erase 会让后续迭代器失效,必须使用返回的新迭代器或标准算法。

3

不必要的对象复制

范围循环写 auto item 会复制,修改或只读大对象时应使用引用。

4

保存失效地址

push_back 触发扩容后,之前指向元素的指针可能失效。不要长期保存。

本课通用调试法

先准备一个最小输入,只保留能够重现问题的几行数据;再在关键步骤输出变量值,确认程序究竟在哪一步偏离预期。编译错误从第一条开始处理,运行错误则比较“实际结果”和“期望结果”。修复后别只重跑原来的例子,还要增加空输入、边界值和错误输入,防止问题换一个形式再次出现。

Homework

第 13 课课后作业:从模仿到独立完成。

建议按顺序完成。前四题帮助巩固语法和基本操作,第五到第七题要求把多个知识点组合起来,第八题是小项目。每道题都要先写输入、处理、输出三行计划,再开始敲代码;程序运行后至少测试正常情况、边界情况和错误情况。

1

数字列表

读取任意个整数,输出总和、平均值、最大值和所有大于平均值的元素。

2

反向输出

分别用下标和反向迭代器倒序输出 vector,比较两种写法。

3

去除重复

不使用 set,把输入列表中的重复值删除并保留第一次出现顺序。

4

学生查找

按学号查找、修改和删除学生,找不到时不能误改其他记录。

5

座位表

使用二维 vector 表示座位,支持预订、取消、打印和剩余座位统计。

6

库存列表

商品包含编号、名称、价格和库存,实现进货、购买与低库存筛选。

7

播放列表

支持尾部添加、按位置插入、删除指定歌曲和移动歌曲顺序。

8

成绩管理项目

完成增删改查、平均分、最高分和等级人数统计,所有操作通过菜单执行。

本课验收标准:能解释 array 与 vector 的选择;增删改查覆盖空列表和找不到目标;遍历对象时正确使用引用;不发生下标越界或删除导致的迭代器错误。

提交内容应包括源代码、三组测试输入与输出、一个曾经出现的错误及修复方法。最后请用自己的话解释本课最重要的概念;如果只能照着代码念,还需要再独立重写一次核心例子。

Lesson 13 summary

容器保存一组数据,算法规定怎样遍历和改变它们。

arrayvectoriteratorerase顺序查找

下一课会让列表按不同规则排序,并在有序数据中进行更快的二分查找。