3. 基于范围for与结构化绑定的容器遍历重构
重构目标与遍历语义
传统下标遍历通常写成 for (size_t i = 0; i < v.size(); ++i),再通过 v[i] 访问元素。这种写法把“索引”暴露在循环体中,容易产生越界、类型不匹配、修改语义不清晰等问题。范围 for 与结构化绑定把关注点从“下标”转移到“元素本身”,尤其适合遍历 std::map、std::vector<std::pair>、std::tuple 等复合元素容器。
重构时最关键的是区分三种绑定形式:auto 拷贝、auto& 可修改引用、const auto& 只读引用。它们直接决定遍历过程中能否修改元素、是否产生额外拷贝,以及结构化绑定中的名字是否可写。
重构示例:学生成绩容器
下面以 std::vector<Student> 为例,其中 Student 是包含 id 与 score 的结构体。先给出传统下标版本,再逐步重构为范围 for 与结构化绑定,并验证元素修改行为。
#include <iostream>
#include <vector>
#include <string>
#include <numeric>
struct Student {
int id;
int score;
};
int main() {
std::vector<Student> students{
{101, 78}, {102, 85}, {103, 92}, {104, 66}
};
// 1. 传统下标遍历:给每个学生加 5 分
for (std::size_t i = 0; i < students.size(); ++i) {
students[i].score += 5;
}
// 2. 范围 for + auto& 结构化绑定:等价修改
for (auto& [id, score] : students) {
score += 5; // 直接修改容器元素
}
// 3. const auto& 只读遍历:统计总分
int total = 0;
for (const auto& [id, score] : students) {
total += score;
// score += 1; // 编译错误:只读引用不可修改
}
// 4. auto 拷贝遍历:修改副本,不影响容器
for (auto [id, score] : students) {
score += 100; // 只改副本
}
// 验证:容器内分数未被第 4 步影响
for (const auto& [id, score] : students) {
std::cout << "id=" << id << " score=" << score << '\n';
}
std::cout << "total=" << total << '\n';
}
关键验证点
- 修改语义验证:第 2 步使用
auto&后,容器内score应比初始值增加 10(第 1 步 +5,第 2 步 +5)。 - 只读约束验证:第 3 步中若取消注释
score += 1;,编译器应报错,证明const auto&阻止了修改。 - 拷贝隔离验证:第 4 步使用
auto拷贝,循环内score += 100不会影响容器,最终输出分数应与第 2 步结束后一致。 - 结构化绑定名字:
id与score是绑定到元素成员的别名,不是独立变量;对score的修改会直接反映到容器元素上(当绑定为引用时)。
重构时优先使用
const auto& 进行只读遍历,需要修改时再改为 auto&。避免无意中使用 auto 拷贝,尤其在元素较大或包含动态资源时。结构化绑定中的
id 和 score 不能重新绑定到其他对象,也不能在循环体内对它们取地址后长期保存;它们只是当前元素的成员别名。