在 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 对应当前取出的商品对象,顺序千万不能写反。
