一、简介
重点讨论如何使用STL填充范围(range )或容器。
本文深入探讨了STL的填充算法,重点关注了填充范围(range)或容器的操作。首先介绍了std::fill
和std::uninitialized_fill
两个算法,它们分别用于将范围或未初始化的内存中的元素填充为指定的值。接着介绍了std::generate
和std::iota
两个算法,前者用于通过可调用函数生成随机值填充范围,后者用于生成递增序列填充范围。此外,还介绍了*_n
系列算法,如std::fill_n
、std::uninitialized_n
和std::generate_n
,它们用于快速填充指定数量的元素。最后,讨论了容器自身的填充方法,包括构造函数和赋值方法。
二、std::fill 和 std::uninitialized_fill
std::fill
接受一个范围和一个值,并将范围中的所有元素设置为等于这个值。
vector<int> v = {1, 2, 3, 4, 5};
fill(v.begin(), v.end(), 3);
// v contains {3, 3, 3, 3, 3};
std::fill
对每个元素调用operator=
。
std::uninitialized_fill
的功能与 std::fill
基本相同,但它用于填充已经分配但尚未初始化的内存(例如使用 operator new
、malloc
或自定义内存分配器分配的内存)。
该算法将每个元素初始化为传递的值,这意味着它调用该类型的构造函数并传递一个值。因此,std::uninitialized_fill
不会调用赋值operator=
。
示例:
class MyClass
{
public:
explicit MyClass(int i);
private:
int i_;
};
// Allocate a buffer that can contain 5 objects of MyClass
MyClass* myObjects = static_cast<MyClass*>(malloc(5 * sizeof(MyClass)));
// Call constructor on each object, with value 3
std::uninitialized_fill(myObjects, myObjects + 5, 3);
// Use myObjects...
// Call destructor on each object
std::for_each(myObjects, myObjects + 5, [](const MyClass& object){object.~MyClass();});
// Deallocate the buffer
free(myObjects);
myObjects = nullptr;
这在概念上与在数组中进行“placement new
”操作非常相似,但它避免了编译器为进行账目管理而为数组分配未知大小时所带来的缺点。
std::uninitialized_fill
函数提供了一种更好的方法来填充数组。它可以在不知道数组大小的情况下进行操作,避免了编译器进行账目管理的麻烦。这意味着可以简单地使用该函数来填充数组,而不必担心分配内存的问题。
三、std::generate 和 std::iota
std::generate
接受一个范围和一个不带参数的可调用函数(或函数对象),并将调用该函数返回的值赋给范围中的每个元素。
它的典型用法是用随机值填充一个范围:
int getRandomNumber();
vector<int> v = {1, 2, 3, 4, 5};
std::generate(v.begin(), v.end(), getRandomNumber);
// v may contain {7, 257, -3, 18, -44};
而 std::iota
会用前缀operator++
获取递增的值,并将这些值填充到指定的范围内,从给定的值开始。
vector<int> = {1, 2, 3, 4, 5};
iota(v.begin(), v.end(), 10);
// v now contains {10, 11, 12, 13, 14}
四、* _n 系列算法
std::fill
, std::uninitialized_fill
和 std::generate
有各自的_n*
版本,即 std::fill_n
、std::uninitialized_n
和 std::generate_n
,它们都接受一个输出迭代器和一个大小。
template <typename OutputIterator, class Size, class T>
OutputIterator fill_n(OutputIterator first, Size count, const T& value);
如果需要填充集合的前n个元素,这些算法很有用:
std::vector<char> v = {'h', 'e', 'l', 'l', 'o', '!'};
std::fill_n(begin(v), 3, 'a');
// v contains {'a', 'a', 'a', 'l', 'o', '!'};
它们还可用于向集合追加几个相同的值。例如std::generate_n
通常可以用来填充一个随机数字的空集合:
int randomNumberGenerator()
{
static std::random_device random_device;
static std::mt19937 engine{random_device()};
static std::uniform_int_distribution<> distribution(1,6);
return distribution(engine);
}
std::vector<int> numbers;
std::generate_n(std::back_inserter(numbers), 10, randomNumberGenerator);
// numbers may now contain {4, 1, 1, 6, 6, 3, 2, 5, 4, 1}
在这个特殊的例子中,同样可以为10个元素保留分配的大小,在这里只是为了演示算法。
五、容器方法
vector
、deque
、list
和string
都有可以给它们填充值的方法:它们的构造函数和赋值方法。
构造函数的填充方式:
vector<string> v(3, "hello");
// vector now contains {“hello”, “hello”, “hello”},
更准确地说,它包含表示这些字符的字符串。
在这里,vector
从传递的值(“hello”)构建一个字符串,然后通过复制构造从该字符串创建其他元素。
assign
方法根据传递的值构造一个对象,然后在每个元素上调用operator=
,用这个构造的对象为其赋值:
vector<string> v;
v.assign(3, “hello”);
// vector now contains {“hello”, “hello”, “hello”},
六、总结
通过本文的学习,读者可以深入了解STL的填充算法,并掌握如何轻松操作范围和容器。使用这些算法能够简化代码、提高效率,并使代码更易读。std::fill
和std::uninitialized_fill
可以快速将范围或未初始化的内存中的元素填充为指定的值,而std::generate
和std::iota
则可以生成随机值或递增序列填充范围。此外,*_n
系列算法可以快速填充指定数量的元素。同时,本文还介绍了容器自身的填充方法,包括构造函数和赋值方法,读者可以根据具体需求选择合适的方法。通过学习本文,读者将能够更加灵活和高效地利用STL的填充算法来操作范围和容器。