C++ accumulate 求和

在 C++ 中,我们可以使用 <numeric> 头文件中的 std::accumulate() 函数对容器元素进行累加求和。accumulate 在英文里是 “累积、累加” 的意思。我们只需要传入起止位置和初始值,写一行代码就能算出总和,省去了手写循环的麻烦。

std::accumulate() 的基本用法

std::accumulate() 用来将指定 [first, last) 区间内的所有元素,从给定的初始值开始依次相加,并返回最终的计算结果。它只负责读取和计算,不会修改原容器中的任何元素。

注意 前面学过的 std::sort()、std::find() 等算法都位于 <algorithm> 头文件中,而 std::accumulate() 定义在 <numeric> 头文件中,使用前小伙伴们千万不要漏写 #include <numeric>。

语法:

auto result = std::accumulate(first, last, init);

说明:

std::accumulate() 接收以下 3 个参数:

  • first(必选):累加范围的起始迭代器,比如 nums.begin(),包含在计算范围内。
  • last(必选):累加范围的结束边界迭代器,比如 nums.end(),实际计算的是 [first, last) 左闭右开区间。
  • init(必选):累加的初始值。函数会以 init 作为起点,依次把区间内的每个元素加进去,最后返回累加后的总和。

示例 1:计算整数容器的总和

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

int main() {
    std::vector<int> nums = {80, 90, 70};

    // 从初始值 0 开始累加
    int sum1 = std::accumulate(nums.begin(), nums.end(), 0);
    std::cout << sum1 << '\n';

    // 从初始值 100 开始累加
    int sum2 = std::accumulate(nums.begin(), nums.end(), 100);
    std::cout << sum2 << '\n';
    return 0;
}

运行结果如下。

240
340

分析:

在这个例子中,计算 sum1 时我们将初始值设为了 0,整个过程相当于执行 0 + 80 + 90 + 70,返回结果 240。

计算 sum2 时则把初始值设为了 100,相当于在基础分 100 之上继续加分,即 100 + 80 + 90 + 70,最终得到 340。

如果传入的是空容器,std::accumulate() 不会报错,而是直接返回初始值 init。此外,调整起止迭代器还能只对部分元素求和,比如传入 nums.begin() 和 nums.begin() + 2 只累加前 2 个元素。

初始值类型:0 与 0.0

在调用 std::accumulate() 时,第 3 个参数 init 不仅决定了 “从多少开始加”,它的数据类型还直接决定了内部累加过程和最终返回值的数据类型。

当容器里存放的是 double 小数时,如果我们习惯性地把初始值写成整数 0,就会遇到小数部分丢失的问题。请看下面例子。

示例 2:累加 double 小数

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

int main() {
    std::vector<double> nums = {1.5, 2.5, 3.5};

    // 初始值写整数 0
    auto sum1 = std::accumulate(nums.begin(), nums.end(), 0);

    // 初始值写小数 0.0
    auto sum2 = std::accumulate(nums.begin(), nums.end(), 0.0);

    std::cout << sum1 << '\n';
    std::cout << sum2 << '\n';
    return 0;
}

运行结果如下。

6
7.5

分析:

“咦?1.5 + 2.5 + 3.5 明明等于 7.5,为什么 sum1 算出来却是 6 呢?”

原因就在于整数 0 是 int 类型。当我们传入 0 时,std::accumulate() 内部就会按 int 类型创建临时变量来保存每一步的累加结果。对于这个例子来说,计算 sum1 相当于执行了下面的代码。

int sum = 0;
sum = sum + 1.5;    // 截断小数,sum 变为 1
sum = sum + 2.5;    // 截断小数,sum 变为 3
sum = sum + 3.5;    // 截断小数,sum 变为 6

每次把小数赋回 int 类型的 sum 时,都会发生隐式类型转换,小数部分 .5 被直接截断丢弃,最终算出来就只有 6。而在计算 sum2 时,我们把初始值写成了 double 类型的 0.0,内部就会全程使用 double 变量累加,准确算出 7.5。

提示 平时求和时,小伙伴们只需要记住一条简单规则:累加 int 整数初始值写 0,累加 double 小数初始值写 0.0。如果担心多个大整数相加超出 int 范围溢出,把初始值写成 0LL 就会自动按 long long 类型安全累加。

自定义累积规则

默认情况下,std::accumulate() 使用加号 + 把元素一个个加起来。如果我们需要计算一组数字的 “连乘积”,或者对结构体容器中的某个成员进行汇总,就可以传入第 4 个参数来指定计算规则。

语法:

auto result = std::accumulate(first, last, init, op);

说明:

第 4 个参数 op 是自定义的二元计算函数或 Lambda 表达式,内部需要通过 return 返回计算后的新累积值。它依次接收以下 2 个参数:

  • 第 1 个参数:表示当前已经累积的结果,数据类型与初始值 init 一致。
  • 第 2 个参数:表示当前取出的容器元素,数据类型与容器中的元素一致。

1. 计算元素的连乘积

如果我们要把容器里的所有整数乘起来,只需要在 Lambda 表达式中返回两个参数的乘积即可。

示例 3:计算元素的连乘积

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

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

    // 计算连乘时初始值设为 1
    int product = std::accumulate(nums.begin(), nums.end(), 1,
        [](int acc, int n) {
            return acc * n;
        });

    std::cout << product << '\n';
    return 0;
}

运行结果如下。

24

分析:

在这个例子中,我们将初始值设为 1,并传入 Lambda 表达式定义乘法规则。其中第 1 个参数 acc 保存当前的累积乘积,第 2 个参数 n 接收每次取出的元素。程序依次计算 1 * 2 = 2、2 * 3 = 6、6 * 4 = 24,最终返回 24。

小伙伴们要注意,计算连乘时的初始值千万不要习惯性写成 0,否则 0 乘以任何数结果都只会是 0。

2. 汇总结构体成员

当容器里存放的是自定义的结构体或类对象时,编译器不知道应该拿哪个成员去相加,此时我们同样可以通过第 4 个参数指定累加逻辑。

示例 4:计算购物车商品总价

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

struct Item {
    std::string name;
    double price;
};

int main() {
    std::vector<Item> cart = {
        {"笔记本", 12.5},
        {"中性笔", 3.5},
        {"文件夹", 8.8}
    };

    double total = std::accumulate(cart.begin(), cart.end(), 0.0,
        [](double sum, const Item& item) {
            return sum + item.price;
        });

    std::cout << "总价:" << total << '\n';
    return 0;
}

运行结果如下。

总价:24.8

分析:

在这个例子中,由于商品价格包含小数,我们将初始值设为了 0.0。每轮计算执行 return sum + item.price; 把单价加进当前总和,最终准确算出 24.8。

编写这类自定义汇总时,大家要特别留意 Lambda 两个参数的先后顺序:第 1 个参数 double sum 对应初始值 0.0 的类型,第 2 个参数 const Item& item 对应当前取出的商品对象,顺序千万不能写反。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号