40、STL算法(数值与变换):accumulate、transform、copy、replace

说实话,STL算法库是我日常开发中使用频率最高的工具之一。尤其是数值计算和元素变换这组算法,几乎每个项目都会碰到。今天我们就来聊聊四个最常用的家伙:accumulatetransformcopyreplace

嗯,先别急着看代码。我建议你先理解它们的定位——accumulate 是「归约」,transform 是「映射」,copy 是「搬运」,replace 是「替换」。说白了,这四兄弟覆盖了数据处理中最常见的四种操作模式。

1. accumulate:累加与自定义归约

std::accumulate 定义在 头文件中。它的核心作用是对一个范围内的元素做「累积」操作。默认是加法,但你可以传入任意二元操作符。

#include <numeric>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};

    // 默认累加:1+2+3+4+5 = 15
    int sum = std::accumulate(data.begin(), data.end(), 0);
    std::cout << "sum = " << sum << std::endl;

    // 自定义操作:累乘 1*2*3*4*5 = 120
    int product = std::accumulate(data.begin(), data.end(), 1,
                                  std::multiplies<int>());
    std::cout << "product = " << product << std::endl;

    return 0;
}

我的经验:初始值类型决定了返回类型。如果你传 0(int),返回 int;如果传 0.0(double),返回 double。我在项目中曾因为初始值类型写错,导致浮点精度丢失,排查了半天。

你想想看,accumulate 的第三个参数不仅仅是初始值,它还参与了类型推导。这一点很多人会忽略。

2. transform:批量映射

std::transform 是「一对一」变换的利器。它遍历输入范围,对每个元素应用函数,结果写入输出范围。输出范围可以和输入范围重叠(就地变换)。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst(src.size());

    // 一元变换:每个元素乘以2
    std::transform(src.begin(), src.end(), dst.begin(),
                   [](int x) { return x * 2; });

    // 二元变换:两个序列对应元素相加
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {10, 20, 30};
    std::vector<int> c(a.size());

    std::transform(a.begin(), a.end(), b.begin(), c.begin(),
                   [](int x, int y) { return x + y; });

    return 0;
}

关键点:transform 有两种重载——一元版本和二元版本。二元版本需要两个输入范围,第三个参数是第二个范围的起始迭代器。注意:第二个范围的长度必须 >= 第一个范围。

我个人习惯用 transform 替代手写 for 循环。代码更简洁,意图更明确。而且编译器对 STL 算法的优化通常比手写循环更好。

3. copy:元素搬运工

std::copy 是最基础的复制算法。它把一段范围内的元素复制到另一段连续内存中。听起来简单,但有几个坑需要注意。

#include <algorithm>
#include <vector>
#include <iostream>
#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());

    // 正确做法1:提前分配空间
    dst.resize(src.size());
    std::copy(src.begin(), src.end(), dst.begin());

    // 正确做法2:使用back_inserter
    std::vector<int> dst2;
    std::copy(src.begin(), src.end(), std::back_inserter(dst2));

    // 输出到控制台
    std::copy(dst.begin(), dst.end(),
              std::ostream_iterator<int>(std::cout, " "));

    return 0;
}

我曾经踩过的坑:copy 往空 vector 里写数据,忘记提前 resize 或使用 back_inserter,结果程序直接崩溃。记住:copy 不会自动扩容,目标容器必须有足够的空间。

为什么会这样?因为 copy 只是做内存级别的复制,它不关心目标容器的容量管理。这是 C++ 的设计哲学——不为你做你不明确要求的事情。

4. replace:条件替换

std::replacestd::replace_if 用于替换范围内的元素。前者替换特定值,后者替换满足条件的值。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};

    // 将所有2替换为99
    std::replace(data.begin(), data.end(), 2, 99);

    // 将所有大于3的元素替换为0
    std::replace_if(data.begin(), data.end(),
                    [](int x) { return x > 3; }, 0);

    // 注意:replace是就地修改,不会创建新容器
    for (int x : data) {
        std::cout << x << " ";
    }

    return 0;
}

小技巧:如果你需要保留原数据,先 copy 一份再 replace。或者用 std::transform 配合条件表达式实现「复制+替换」一步到位。

知识体系总览

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

STL数值与变换算法核心四兄弟 accumulate 归约 / 累积 transform 映射 / 变换 copy 复制 / 搬运 replace 替换 / 修改 输入范围:[first, last) 或 多个序列 输出:单个值 / 新序列 / 原地修改 核心区别:accumulate 返回一个值,transform/copy/replace 操作序列 共同点:都接受迭代器范围,都支持 lambda 自定义操作

综合示例:数据清洗流水线

在实际项目中,这四个算法经常组合使用。下面是一个数据清洗的例子:

#include <numeric>
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>

int main() {
    std::vector<int> raw = {1, -2, 3, -4, 5, 0, -6, 7};

    // 第一步:copy 一份原始数据
    std::vector<int> cleaned;
    std::copy(raw.begin(), raw.end(), std::back_inserter(cleaned));

    // 第二步:replace_if 将所有负数替换为0
    std::replace_if(cleaned.begin(), cleaned.end(),
                    [](int x) { return x < 0; }, 0);

    // 第三步:transform 将每个元素加1
    std::transform(cleaned.begin(), cleaned.end(), cleaned.begin(),
                   [](int x) { return x + 1; });

    // 第四步:accumulate 计算总和
    int total = std::accumulate(cleaned.begin(), cleaned.end(), 0);

    std::cout << "清洗后数据: ";
    std::copy(cleaned.begin(), cleaned.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n总和: " << total << std::endl;

    return 0;
}

输出结果:

清洗后数据: 2 1 4 1 6 1 1 8

总和: 24

你看,四个算法配合起来,数据处理的流水线非常清晰。每个步骤只做一件事,代码可读性极高。这也是我为什么一直推荐团队多用 STL 算法——它强迫你把逻辑拆分成小的、可组合的步骤。

最后说一句:accumulate 在 C++17 之后有了并行版本 std::reduce,如果你处理的数据量很大,可以考虑升级。不过那是另一个话题了,今天先到这里。


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