突破编程_C++_STL教程( fill 算法)

本文详细介绍了C++标准模板库中的std::fill算法,包括其概念、基本和高级用法,以及如何与其他STL算法结合使用。同时讨论了优化std::fill操作的建议,如减少拷贝、选择正确的迭代器和利用数据结构特性。
摘要由CSDN通过智能技术生成

1 std::fill 算法的概念与用途

std::fill 是C++标准模板库(STL)中的一个算法,它用于为容器(如向量、列表、数组等)中指定范围内的所有元素分配相同的值。std::fill 算法的概念基于两个主要部分:范围和值。范围指的是容器中的一段连续元素,通常由一个起始迭代器和一个结束迭代器定义;值则是要分配给这段范围内所有元素的单个值。

std::fill 的主要用途包括:

  • 初始化容器:当创建一个新的容器并希望其所有元素都具有相同的初始值时,可以使用 std::fill。例如,可以创建一个整数向量并用零填充它。
  • 重置容器内容:如果需要将容器中的所有元素重置为某个特定值,std::fill 是一个高效的选择。这在准备容器以进行新的计算或数据处理时特别有用。
  • 快速赋值操作:对于大型容器,使用 std::fill 可以比使用循环结构更快地填充相同的值,因为它利用了 STL 算法的内部优化。

使用 std::fill 时,需要注意以下几点:

  • 需要包含 <algorithm> 头文件以访问 std::fill。
  • 提供给 std::fill 的值类型必须与容器中的元素类型兼容。
  • 结束迭代器指向的是要填充范围之外的第一个元素,因此范围实际上包括起始迭代器指向的元素,但不包括结束迭代器指向的元素。

2 std::fill 算法基础

2.1 std::fill 算法的定义与语法

(1)定义:

std::fill 函数定义于 STL 的 <algorithm> 算法库中,该库提供了大量用于在元素范围上进行操作的函数,如查找、排序、计数等。范围通常定义为 [first, last),其中 first 是指向范围起始位置的迭代器,而 last 是指向范围结束位置的下一个元素的迭代器,也就是说,last 并不包含在填充范围内。

(2)语法:

std::fill 函数的语法如下:

std::fill(iterator start, iterator end, const T& value);

其中:

  • iterator start:指向要填充范围起始位置的迭代器。
  • iterator end:指向要填充范围结束位置的下一个元素的迭代器。
  • const T& value:要赋给范围内所有元素的值。这里的T是一个类型占位符,代表 value 的实际类型,它必须与容器元素的类型兼容。

(3)返回值:

std::fill 函数没有返回值,即其返回类型为 void。该函数会直接修改容器中的元素,将指定范围内的所有元素设置为给定的 value。

2.2 std::fill 算法的基本使用示例

std::fill 算法的基本使用示例非常直观,它涉及到定义一个容器(比如 std::vector),然后使用 std::fill 函数将容器中的元素填充为指定的值。下面是一个简单的例子:

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() 
{  
    // 创建一个包含10个元素的整数向量,并初始化为0(这是vector的默认行为)  
    std::vector<int> vec(10);  
  
    // 使用 std::fill 算法将向量中的所有元素填充为 12  
    std::fill(vec.begin(), vec.end(), 12);  
  
    // 输出向量以验证填充结果  
    for (const auto& elem : vec) {  
        std::cout << elem << ' ';  
    }  
    std::cout << std::endl;  
  
    return 0;  
}

上面代码的输出为:

12 12 12 12 12 12 12 12 12 12

这个例子首先包含了必要的头文件:<iostream> 用于输入输出,<vector> 用于使用 std::vector 容器,<algorithm> 包含了 std::fill 算法。

在 main 函数中,创建了一个包含 10 个元素的 std::vector类型的向量vec。默认情况下,std::vector 的构造函数会将所有元素初始化为该类型的默认值,对于 int 类型,默认值是0。

接着,调用了 std::fill 函数,传递了三个参数:向量的起始迭代器(vec.begin()),向量的结束迭代器(vec.end()),以及要填充的值(12)。std::fill函数会遍历向量中的每个元素,并将它们设置为指定的值。

最后,使用一个范围 for 循环来遍历向量并输出每个元素的值,以验证 std::fill 函数是否正确地将所有元素填充为了 12。

3 std::fill 算法的高级用法

3.1 使用 std::fill_n 函数填充指定数量的元素

std::fill_n 是 C++ 标准模板库(STL)中的一个算法,用于填充容器中的前 n 个元素为指定的值。当只需要填充容器中一部分元素而不是全部时,可以使用这个函数。它的语法如下:

std::fill_n(iterator first, size_t count, const T& value);

其中:

  • iterator first:指向要填充的第一个元素的迭代器。
  • size_t count:要填充的元素数量。
  • const T& value:要赋给指定数量元素的值。

std::fill_n 将会从 first 指向的元素开始,填充接下来的 count 个元素为 value。如果 first 指向的容器中的元素少于 count 个,则行为是未定义的(即可能会导致程序崩溃或其他错误)。

以下是一个使用 std::fill_n 的基本示例:

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() 
{  
    // 创建一个包含 10 个元素的整数向量,并初始化为 0  
    std::vector<int> vec(10);  
  
    // 使用 std::fill_n 算法填充向量中的前 5 个元素为 12  
    std::fill_n(vec.begin(), 5, 12);  
  
    // 输出向量以验证填充结果  
    for (const auto& elem : vec) {  
        std::cout << elem << ' ';  
    }  
    std::cout << std::endl;  
  
    return 0;  
}

上面代码的输出为:

12 12 12 12 12 0 0 0 0 0

这个例子创建了一个大小为 10 的整数向量 vec,然后使用 std::fill_n 填充前 5 个元素为 12。
需要注意的是,当使用 std::fill_n 时,需要确保提供的 count 不超过从 first 到容器末尾的元素数量。如果超过了,程序可能会访问到未定义的内存区域,导致不可预测的行为。因此,在使用 std::fill_n 时,务必检查容器大小以及尝试填充的元素数量。

3.2 std::fill 算法与其他 STL 算法结合使用

STL 算法库提供了许多其他算法,它们可以单独使用,也可以与 std::fill 结合使用,以实现更复杂的序列操作。下面是一些 std::fill 与其他 STL 算法结合使用的示例和详细讲解。

(1)与遍历算法结合使用

遍历算法如 std::for_each 可以用于对容器中的每个元素执行某个操作。可以先用 std::fill 填充容器,然后用 std::for_each 遍历并处理这些元素。

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
void print_element(int value) {  
    std::cout << value << ' ';  
}  
  
int main() {  
    std::vector<int> vec(10);  
    std::fill(vec.begin(), vec.end(), 12); // 填充容器  
    std::for_each(vec.begin(), vec.end(), print_element); // 遍历并打印每个元素  
    return 0;  
}

上面代码的输出为:

12 12 12 12 12 12 12 12 12 12

在这个例子中,std::fill 首先被用来将vec的所有元素填充为 12。然后,std::for_each 被用来遍历容器中的每个元素,并对每个元素调用 print_element 函数,从而打印出每个元素的值。

(2)与转换算法结合使用

转换算法,如 std::transform,可以对容器中的每个元素应用一个函数或操作,并将结果存储到另一个容器中。可以先用 std::fill 填充一个容器,然后使用 std::transform 转换这些元素。

#include <iostream>  
#include <vector>  
#include <algorithm>  
#include <iterator>  
#include <functional> // 用于 std::plus  
  
int main() {  
    std::vector<int> vec(10);  
    std::fill(vec.begin(), vec.end(), 1); // 填充容器为1  
  
    std::vector<int> result(vec.size());  
    std::transform(vec.begin(), vec.end(), result.begin(), std::bind(std::plus<int>(), std::placeholders::_1, 5)); // 每个元素加5  
  
    // 输出结果  
    for (int val : result) {  
        std::cout << val << ' ';  
    }  
    std::cout << std::endl;  
  
    return 0;  
}

上面代码的输出为:

6 6 6 6 6 6 6 6 6 6

在这个例子中,std::fill 首先被用来将 vec 的所有元素填充为 1。然后,std::transform 被用来将 vec 中的每个元素加5,并将结果存储在 result 容器中。这里使用了 std::bind 和 std::plus 来创建一个加 5 的函数对象。

(3)与排序和查找算法结合使用

还可以将 std::fill 与排序和查找算法结合使用。例如,可以先填充一个容器,然后对其进行排序,并使用查找算法来查找特定的元素。

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() {  
    std::vector<int> vec = {5, 3, 1, 4, 2};  
    std::fill(vec.begin(), vec.begin() + 3, 10); // 将前三个元素填充为10  
    std::sort(vec.begin(), vec.end()); // 对容器进行排序  
  
    // 查找元素  
    auto it = std::find(vec.begin(), vec.end(), 10);  
    if (it != vec.end()) {  
        std::cout << "Found 10 at position: " << std::distance(vec.begin(), it) << std::endl;  
    } else {  
        std::cout << "10 not found." << std::endl;  
    }  
  
    return 0;  
}

上面代码的输出为:

Found 10 at position: 2

在这个例子中,std::fill 首先被用来将 vec 的前三个元素填充为 10。然后,std::sort 被用来对容器进行排序。最后,std::find 被用来查找值为 10 的元素,并输出其位置。

这些只是 std::fill 与其他 STL 算法结合使用的一些基本示例。实际上,STL 提供了大量算法,它们可以灵活地组合在一起,以实现各种复杂的序列操作和处理任务。

3.3 自定义填充函数

std::fill算法本身并不直接支持自定义填充函数,因为它接受的是一个值而不是一个函数作为填充参数。然而,你可以通过结合其他STL算法,如std::generate或std::generate_n,来实现自定义填充函数的功能。

std::generate和std::generate_n算法可以生成一系列的值,并将它们赋给序列中的元素。它们接受一个函数对象(或lambda表达式),这个函数对象被用来生成每个元素的值。

假设有一个整数向量,并且你想要用自定义函数来填充它,例如,将每个元素设置为它的索引的平方。则可以这样做:

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

int main() {
	std::vector<int> vec(10);

	// 使用std::generate和自定义生成器函数填充容器  
	int offset = 0;
	std::generate(vec.begin(), vec.end(), [&]() {
		int val = offset * offset;
		offset++;
		return val;
		});

	// 输出结果  
	for (const auto& elem : vec) {
		std::cout << elem << ' ';
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

0 1 4 9 16 25 36 49 64 81

上面代码使用 std::generate 来填充向量 vec。std::generate 接受三个参数:一个输出迭代器(指向要填充序列的起始位置),要生成的元素数量,以及一个函数对象或 lambda 表达式,用于生成每个元素的值。

注意:std::generate 的函数对象或 lambda 表达式的入参个数为 0 ,所以这里采用中间变量 offset 做数组遍历偏移处理。

4 优化 std::fill 操作的建议与技巧

优化 std::fill 操作的建议与技巧涉及多个方面,包括减少不必要的拷贝、选择正确的迭代器类型、以及利用底层数据结构的特性。下面是一些详细的建议:

(1)减少不必要的拷贝

在调用 std::fill 时,如果填充的值是一个大型对象或者需要复杂计算的对象,那么每次填充时都会进行拷贝操作。为了减少不必要的拷贝,可以考虑以下方法:

  • 使用引用或指针:如果可能的话,可以传递对要填充的值的引用或指针,而不是值本身。这样可以避免在每次填充时都创建新的对象。
  • 使用常量表达式:如果填充的值是一个常量表达式,那么编译器可能会进行优化,将其存储在寄存器中而不是每次从内存中加载。

(2)选择正确的迭代器类型

std::fill 需要迭代器来指定填充的范围。选择正确的迭代器类型可以影响性能:

  • 随机访问迭代器:对于支持随机访问迭代器的容器(如 std::vector 和 std::array),std::fill 可以直接计算范围的大小,并使用循环来填充元素。这是最高效的方式。
  • 前向迭代器:对于只支持前向迭代器的容器(如 std::list),std::fill 需要逐个遍历元素来填充。这种情况下,性能可能较差,因为无法直接计算范围的大小。

(3)利用底层数据结构的特性

不同的数据结构有不同的特性,可以影响 std::fill 的性能:

  • 连续存储的数据结构:对于连续存储的数据结构(如 std::vector),std::fill 可以利用内存连续性来优化操作。例如,它可以使用 memset 或类似的底层函数来一次性填充多个字节。
  • 非连续存储的数据结构:对于非连续存储的数据结构(如 std::list 或 std::deque),std::fill 需要逐个遍历和修改元素,这通常比连续存储的情况要慢。

(4)考虑并行化

如果你的应用程序允许并行处理,并且填充操作不会引入数据竞争,那么可以考虑使用并行算法来加速 std::fill。C++17 引入了并行算法库 ,它提供了并行版本的STL算法,包括 std::fill。然而,请注意,并行化并不总是带来性能提升,特别是在数据规模较小或并行化开销较大的情况下。

(5)使用内存预分配

如果已经知道将要填充的容器的大小,并且这个大小不会改变,那么可以在创建容器时预分配足够的内存。这样可以避免在填充过程中进行动态内存分配,从而提高性能。

(6)避免不必要的同步

在多线程环境中使用 std::fill 时,需要确保对容器的访问是线程安全的。不必要的同步操作(如互斥锁)可能会降低性能。如果可能的话,尽量在填充操作之前或之后进行同步,而不是在填充过程中。

(7)总结

优化 std::fill 操作的建议与技巧包括减少不必要的拷贝、选择正确的迭代器类型、利用底层数据结构的特性、考虑并行化、使用内存预分配以及避免不必要的同步。在实际应用中,需要根据具体的情况和需求来选择合适的优化策略。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值