13. 常用算术生成算法:accumulate、fill
算术生成算法,说白了就是帮我们做「累加」和「批量赋值」的。这两个函数在 STL 里不算花哨,但实战中几乎天天见。我个人习惯把它们归为「基础工具类」—— 你未必每次都用,但用的时候往往能省下好几行手写循环。
13.1 accumulate:累加的艺术
accumulate 定义在 头文件里。它的核心作用就一个:对一个区间内的元素做累加。但别小看它,我见过不少新手自己手写累加循环,结果漏了初始值或者类型转换出问题。
基本用法
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 从 0 开始累加
int sum = std::accumulate(vec.begin(), vec.end(), 0);
std::cout << sum << std::endl; // 输出 15
return 0;
}
第三个参数是初始值。这个初始值很关键——它不光决定累加的起点,还决定了返回类型。你想想看,如果传 0,返回 int;如果传 0.0,返回 double。我在项目中遇到过有人传 0 去累加浮点数,结果精度丢了一截,排查了半天。
自定义累加逻辑
accumulate 还有第四个参数——一个二元操作函数。你可以用它实现「累乘」、「累减」甚至更复杂的操作。
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 累乘:从 1 开始乘
int product = std::accumulate(vec.begin(), vec.end(), 1,
[](int a, int b) { return a * b; });
std::cout << product << std::endl; // 输出 120
return 0;
}
这里我用了 lambda 表达式。其实你也可以传函数指针或者函数对象。我个人习惯用 lambda,因为代码更紧凑,逻辑一目了然。
13.2 fill:批量赋值的利器
fill 定义在 头文件里。它的作用很简单:把区间内的所有元素赋成同一个值。听起来像 memset?嗯,但 fill 是类型安全的,不会出现 memset 给非 POD 类型赋值导致的内存问题。
基本用法
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec(5); // 5 个默认初始化的元素
std::fill(vec.begin(), vec.end(), 42);
for (int v : vec) {
std::cout << v << " "; // 输出 42 42 42 42 42
}
return 0;
}
你可能会问:直接用 vector<int> vec(5, 42) 不就行了?没错,构造时就能指定初始值。但 fill 的威力在于运行时重新赋值。比如你有一个已经存在的容器,想把它全部重置为某个值,这时候 fill 就派上用场了。
fill_n:指定个数的填充
fill_n 是 fill 的变体,它接受一个起始迭代器和个数,而不是区间。我个人觉得 fill_n 在某些场景下更直观。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec(10);
// 从 vec.begin() 开始,填充 5 个元素为 99
std::fill_n(vec.begin(), 5, 99);
for (int v : vec) {
std::cout << v << " "; // 输出 99 99 99 99 99 0 0 0 0 0
}
return 0;
}
13.3 实战中的坑与技巧
这两个算法看起来简单,但实际项目中容易踩坑。我挑几个常见的说说。
accumulate 的类型陷阱
刚才提到初始值决定返回类型。看这个例子:
std::vector<double> prices = {19.99, 5.50, 3.75};
double total = std::accumulate(prices.begin(), prices.end(), 0);
// total 的值是 28.0,而不是 29.24!
为什么会这样?因为初始值 0 是 int,累加过程中所有 double 都被截断成 int 再相加。正确写法是传 0.0:
double total = std::accumulate(prices.begin(), prices.end(), 0.0);
// 正确结果 29.24
fill 与 resize 的配合
有时候你想把一个 vector 全部填成某个值,但不确定它当前的大小。我的做法是:
std::vector<int> vec;
// ... 一些操作后,vec 可能为空,也可能有元素
// 先确保大小
vec.resize(100);
std::fill(vec.begin(), vec.end(), -1);
或者更简洁:直接用 assign 方法:
vec.assign(100, -1); // 一步到位
嗯,assign 其实内部就是 fill 的封装。但如果你只需要填充已有容器的一部分,fill 更灵活。
13.4 知识体系总览
下面这张图帮你理清这两个算法的关系和使用场景:
13.5 总结
accumulate 和 fill 是 STL 里最基础的算术生成算法。它们不复杂,但用好了能显著提升代码的简洁性和可读性。我个人建议:
- 能用 STL 算法就别手写循环 —— 代码更短,意图更清晰
- 注意 accumulate 的初始值类型 —— 这是最容易踩的坑
- fill 和 fill_n 按需选择 —— 区间已知用 fill,个数已知用 fill_n
- 不要滥用 —— 如果只是构造时初始化,直接用构造函数或 assign 更合适
嗯,这两个算法就聊到这儿。记住一点:STL 算法不是为了炫技,而是为了让你少写 bug。下次遇到累加或批量赋值,先想想能不能用它们。