在C++标准库中,fill是一个定义在<algorithm>头文件里的泛型算法,它的作用是将某个给定值赋给一段迭代器区间内的每一个元素。与直接操作原始内存的memset不同,fill是基于元素级别进行赋值的,因此它能够正确处理任意数据类型,包括自定义类对象。

fill的基本用法
fill的函数签名非常直观,它接受三个参数:区间的起始迭代器、区间的结束迭代器(尾后位置),以及要填入的值。算法会从起始位置开始,逐个元素使用赋值运算符把值写进去,直到到达结束位置为止。由于它依赖迭代器,所以不仅可以用在原生数组上,也能用在vector、list等所有提供前向迭代器的容器。
下面是一段最简单的示例,将一个int数组的全部元素设为值为7:
#include <algorithm>
#include <iostream>
int main() {
int arr[5];
// 将arr[0]到arr[4]都赋值为7
std::fill(arr, arr + 5, 7);
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
return 0;
}
从这段代码可以看出,fill并不关心底层内存布局,只关心“元素”这一概念。如果换成std::vector<int>,只需传入v.begin()和v.end()即可,写法完全一致。这种一致性让代码在面对不同容器时不需要重写初始化逻辑。
fill与memset的本质差异
很多初学者会用memset来初始化数组,但memset是按字节填充的C语言函数。例如memset(arr, 1, sizeof(arr))会把每个字节都设为1,对于一个4字节的int来说,其值会变成0x01010101,也就是16843009,而不是1。这在调试时经常引发难以察觉的bug。
相比之下,fill是对每个元素调用赋值操作。对于int类型,std::fill(arr, arr + n, 1)确实会把每个int设为1。对于非平凡类型,比如含有构造和赋值语义的类,memset会直接破坏对象布局,而fill则通过正常的赋值运算符保持对象语义完整。
| 对比维度 | fill | memset |
|---|---|---|
| 操作单位 | 元素 | 字节 |
| 适用类型 | 任意可赋值类型 | 仅POD类型安全 |
| 语义 | 调用赋值运算符 | 直接写内存 |
| 头文件 | algorithm | cstring |
fill在容器中的实际应用
在使用std::vector时,如果已经分配了空间,可以用fill重新赋值;如果是构造阶段希望全部填值,也可以用构造函数配合fill式的语法,或直接用vector<int> v(n, value)。不过当需要对已存在的容器区间局部修改时,fill的优势就显现出来了。
例如我们只想把一个矩阵的第二行全设为0,可以这样做:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<std::vector<int>> mat(3, std::vector<int>(4, 0));
// 将第二行(索引1)全部填为9
std::fill(mat[1].begin(), mat[1].end(), 9);
for (const auto& row : mat) {
for (int v : row) {
std::cout << v << " ";
}
std::cout << "n";
}
return 0;
}
这种写法清晰表达了“把某一行所有元素设为9”的意图,而不需要手写循环。同时,如果未来矩阵元素换成自定义类型,只要该类型支持赋值,代码无需改动。
fill_n与注意事项
除了fill,标准库还提供fill_n,它接受起始迭代器和元素个数,而不是结束迭代器。这在只需要填充前N个元素时更方便,但要小心不要超出容器边界。
使用fill时应当确保迭代器区间合法且可写。对const容器或只读视图使用fill会导致编译错误,这是类型系统带来的保护。另外,在性能敏感的场景,编译器通常能将fill优化为高效的内存写指令,因此不必担心它比手写循环慢。
#include <algorithm>
#include <vector>
int main() {
std::vector<int> data(10);
// 只填充前5个为3
std::fill_n(data.begin(), 5, 3);
return 0;
}
总的来看,fill是C++中表达“区间赋值”意图最直接、最安全的方式。理解它和memset的区别,能帮助你写出更健壮、可移植且符合现代C++风格的程序。