12. 常用拷贝与替换算法:copy、replace、replace_if、swap

拷贝和替换,说白了就是数据搬运工和整形师。你写代码时肯定遇到过这种场景:要把一个容器里的数据原封不动搬到另一个容器,或者把某些特定值换成别的值。STL 给我们准备了四个非常趁手的工具——copyreplacereplace_ifswap。今天我就带你挨个过一遍,顺便聊聊我踩过的坑。

12.1 copy:最朴素的搬运工

copy 这个算法,名字就说明了一切。它把一段区间里的元素,一个一个复制到目标位置。原型长这样:

OutputIt copy( InputIt first, InputIt last, OutputIt d_first );

三个参数:源区间的起始、结束,以及目标区间的起始。嗯,这里要注意——目标区间必须有足够的空间,否则就是越界访问,程序直接崩给你看。

⚠️ 我曾经犯过的错: 刚用 STL 那会儿,我直接拿一个空 vector 当目标容器,结果 copy 完程序就挂了。后来才明白,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,省心。但如果你对性能有极致要求,提前 resizecopy 会少几次内存分配,稍微快一点。

12.2 replace:精准替换

replace 干的事很简单:把区间里所有等于某个值的元素,换成另一个值。原型:

void replace( ForwardIt first, ForwardIt last,
              const T& old_value, const T& new_value );

它直接在原容器上修改,不会生成新容器。你想想看,这跟 copy 配合 replace_copy 的区别就出来了——replace 是原地改,replace_copy 是边复制边改,原数据不变。

💡 实战场景: 我在做日志分析工具时,需要把某些敏感 IP 地址替换成 "***.***.***.***"。用 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;
}
🔧 我的小技巧: 写 lambda 时,如果条件比较复杂,我习惯先单独写一个函数,再传给 replace_if。这样代码可读性更好,也方便单元测试。

12.4 swap:交换两个容器的灵魂

swap 可能是这四个里最不起眼但最实用的一个。它交换两个同类型容器的内容,时间复杂度是 O(1)——因为它只交换内部指针,不复制元素。

void swap( T& a, T& b );

几乎所有 STL 容器都重载了 swap,包括 vectorstringmap 等等。我建议你养成习惯:需要交换两个容器时,永远用 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 知识体系总览

下面这张图帮你理清这四个算法的关系和使用场景:

拷贝与替换算法核心体系 copy replace replace_if swap 复制区间到目标 替换指定值 按条件替换 交换两个容器 场景: 数据备份 容器间转移 场景: 数据清洗 敏感信息脱敏 场景: 条件过滤 异常值处理 场景: 容器内容互换 排序辅助 ⚠️ 避坑指南 • copy 前确保目标容器有足够空间,或用 back_inserter • replace/replace_if 是原地修改,不影响原数据请用 replace_copy/replace_copy_if • swap 后迭代器不失效,但指针指向的内容会变

12.6 组合使用:copy + replace 的变体

有时候你既想保留原数据,又想得到一份替换后的副本。STL 贴心地提供了 replace_copyreplace_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 比先 copyreplace 更优雅——一次遍历搞定,代码意图也更清晰。

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 算法经过了千锤百炼,比你手写的更健壮、更高效。

公众号:蓝海资料掘金营,微信deep3321