12. 常用拷贝与替换算法:copy、replace、replace_if、swap
拷贝和替换,说白了就是数据搬运工和整形师。你写代码时肯定遇到过这种场景:要把一个容器里的数据原封不动搬到另一个容器,或者把某些特定值换成别的值。STL 给我们准备了四个非常趁手的工具——copy、replace、replace_if、swap。今天我就带你挨个过一遍,顺便聊聊我踩过的坑。
12.1 copy:最朴素的搬运工
copy 这个算法,名字就说明了一切。它把一段区间里的元素,一个一个复制到目标位置。原型长这样:
OutputIt copy( InputIt first, InputIt last, OutputIt d_first );
三个参数:源区间的起始、结束,以及目标区间的起始。嗯,这里要注意——目标区间必须有足够的空间,否则就是越界访问,程序直接崩给你看。
copy 不会自动扩容,你得提前 resize 或者用 back_inserter。
看个例子:
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
// 错误做法:dst 是空的,直接 copy 会越界
// std::copy(src.begin(), src.end(), dst.begin());
// 正确做法一:提前分配空间
dst.resize(src.size());
std::copy(src.begin(), src.end(), dst.begin());
// 正确做法二:用 back_inserter 自动扩容
std::vector<int> dst2;
std::copy(src.begin(), src.end(), std::back_inserter(dst2));
for (int v : dst2) std::cout << v << " ";
return 0;
}
我个人习惯用 back_inserter,省心。但如果你对性能有极致要求,提前 resize 再 copy 会少几次内存分配,稍微快一点。
12.2 replace:精准替换
replace 干的事很简单:把区间里所有等于某个值的元素,换成另一个值。原型:
void replace( ForwardIt first, ForwardIt last,
const T& old_value, const T& new_value );
它直接在原容器上修改,不会生成新容器。你想想看,这跟 copy 配合 replace_copy 的区别就出来了——replace 是原地改,replace_copy 是边复制边改,原数据不变。
replace 一行代码搞定,比手写循环清爽多了。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};
// 把所有 2 替换成 99
std::replace(data.begin(), data.end(), 2, 99);
for (int v : data) std::cout << v << " "; // 输出: 1 99 3 99 4 99 5
return 0;
}
12.3 replace_if:按条件替换
如果替换逻辑不是简单的「等于某个值」,而是「满足某个条件」,那就得上 replace_if。它接受一个一元谓词(返回 bool 的函数或 lambda),对每个元素判断,为 true 就替换。
void replace_if( ForwardIt first, ForwardIt last,
UnaryPredicate p, const T& new_value );
举个例子,把 vector 里所有大于 3 的数换成 0:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> data = {1, 5, 2, 8, 3, 7, 4};
// 把所有大于 3 的元素替换成 0
std::replace_if(data.begin(), data.end(),
[](int x) { return x > 3; }, 0);
for (int v : data) std::cout << v << " "; // 输出: 1 0 2 0 3 0 0
return 0;
}
replace_if。这样代码可读性更好,也方便单元测试。
12.4 swap:交换两个容器的灵魂
swap 可能是这四个里最不起眼但最实用的一个。它交换两个同类型容器的内容,时间复杂度是 O(1)——因为它只交换内部指针,不复制元素。
void swap( T& a, T& b );
几乎所有 STL 容器都重载了 swap,包括 vector、string、map 等等。我建议你养成习惯:需要交换两个容器时,永远用 swap,别手写循环赋值。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6, 7, 8};
std::swap(a, b); // 瞬间完成,不管容器多大
std::cout << "a size: " << a.size() << "\n"; // 5
std::cout << "b size: " << b.size() << "\n"; // 3
return 0;
}
swap 交换后,迭代器和引用不会失效(对于大多数容器)。但如果你之前保存了指向容器元素的指针,交换后指针指向的内容就变了。我在项目中就因为这个查了半天 bug——交换了两个 vector,结果之前保存的指针指向了另一个容器的数据。
12.5 知识体系总览
下面这张图帮你理清这四个算法的关系和使用场景:
12.6 组合使用:copy + replace 的变体
有时候你既想保留原数据,又想得到一份替换后的副本。STL 贴心地提供了 replace_copy 和 replace_copy_if。它们把结果写到另一个容器,原数据纹丝不动。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 2, 4};
std::vector<int> dst;
// 复制的同时把 2 替换成 99
std::replace_copy(src.begin(), src.end(),
std::back_inserter(dst), 2, 99);
// src 不变: 1 2 3 2 4
// dst: 1 99 3 99 4
return 0;
}
我个人觉得,replace_copy 比先 copy 再 replace 更优雅——一次遍历搞定,代码意图也更清晰。
12.7 性能小贴士
| 算法 | 时间复杂度 | 是否原地修改 | 适用容器 |
|---|---|---|---|
| copy | O(n) | 否(写入目标) | 所有支持迭代器的容器 |
| replace | O(n) | 是 | 所有支持迭代器的容器 |
| replace_if | O(n) | 是 | 所有支持迭代器的容器 |
| swap | O(1)(容器级别) | 是 | 所有 STL 容器 |
swap 是唯一选择。别用 assign 或逐元素复制,那会慢到让你怀疑人生。我记得有一次优化代码,把 a = b 改成 swap(a, b),性能直接提升了两个数量级。
好了,这四个算法其实都不复杂,但用好了能省不少事。你写代码时多想想:这个操作能不能用 STL 算法替代?如果能,就别手写循环。STL 算法经过了千锤百炼,比你手写的更健壮、更高效。