将多个元素高效地插入 STL 容器
一、将多个元素插入 STL 容器
在 STL 中,可以大量使用了输出迭代器,例如 std::back_inserter
。虽然这样的迭代器非常方便,但在某些情况下并不想使用它们。例如,将多个连续元素插入 STL 容器。
使用 std::back_inserter
插入多个元素的次优方法是将其与 std::copy
结合使用:
std::vector<int> v;
std::vector<int> newElements = {1, 3, 4, 2, -7, 8};
std::copy(begin(newElements), end(newElements), std::back_inserter(v));
在这里,std::copy
逐个将 newElements
中的每个元素传递给输出迭代器,该迭代器通过调用其 push_back
方法将它们添加到 v
中。这完成了工作:在执行 std::copy
之后,newElements
中的所有元素都已有效地复制到 v
中。
这里的问题是,即使在调用 std::copy
之前,整个元素集合已经知道(知道有多少个元素),但此信息被丢弃了。相反,反复将元素 push_back
到向量 v
中,就好像每次都在发现还有另一个元素要追加一样。这可能会导致vector
多次重新分配。
提前知道要添加多少个元素可以被vector
利用。这使它能够在操作期间最大限度地减少重新分配内存次数:它会在开始操作之前一次性重新分配,而不是在多次调用 push_back
时多次重新分配。
那么,如何在插入vector
时利用此信息呢?只需使用范围插入方法。
在vector
初始化时,使用范围构造函数:
std::vector<int> v{begin(newElements), end(newElements)};
为了将多个新元素追加到现有vector
:
v.insert(end(v), begin(newElements), end(newElements));
请注意,这些方法也存在于其他 STL 容器中,特别是 std::set
和 std::map
。
最后,要将vector
的整个内容替换为 newElements
:
v.assign(begin(newElements), end(newElements));
在执行 assign
之后,所有先前元素都被新的元素替换,无论新旧元素的数量如何。但是,assign
方法不存在于关联容器(如 std::set
和 std::map
)中。
二、std::copy 有用吗?
当然有用。
在上述情况下,std::copy
不合适,因为它盲目地扩展了容器的大小。但有时,不会扩展容器的大小,或者无法提前知道要添加多少个元素。
例如,如果容器已经包含值,并且想要从开头覆盖它们,可以使用 std::copy
:
std::vector<int> v = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
std::vector<int> newElements = {1, 2, 3};
std::copy(begin(newElements), end(newElements), begin(v));
// v 现在包含 {1, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5};
当然,v
必须大于 newElements
才能正常工作。
另一个例子是写入 C 数组:
int a[10] = {};
std::vector<int> newElements = {1, 2, 3};
std::copy(begin(newElements), end(newElements), std::begin(a));
// a 现在包含 {1, 2, 3, 0, 0, 0, 0, 0, 0, 0};
三、std::back_inserter 有用吗?
再次有用!
它通常用于将执行std::copy
以外的任何算法的结果添加到容器中。例如 std::copy_if
:
std::vector<int> v;
std::vector<int> newElements = {1, 3, 2, 4, 3, 2, 2};
std::copy_if(begin(newElements), end(newElements), std::back_inserter(v), [](int i){return i % 2 == 0;});
在这里,没有直接的范围要插入到目标容器中,因此无法使用范围插入方法。
但是,如果知道要插入多少个元素,可以在插入之前执行 reserve
,这样vector
在插入期间就不会多次重新分配。在这种情况下,需要事先执行 count_if
。这可能是过度使用的,取决于这段代码是否被证明是性能瓶颈。
四、总结
要将多个元素插入容器,尽可能使用容器方法:
- 范围插入方法: 适用于已知要插入元素数量的情况,能够最大限度地减少内存重新分配,提升插入效率。
assign
方法: 适用于替换容器中所有元素的情况,但仅适用于序列容器,例如std::vector
。
当无法使用容器方法时,可以使用 std::copy
或 std::back_inserter
结合其他算法:
std::copy
: 适用于覆盖容器中已有元素或写入 C 数组的情况。std::back_inserter
: 适用于将算法结果添加到容器中,例如std::copy_if
。
一些额外的建议:
- 了解容器的特性,选择最适合的插入方法。
- 提前预估要插入的元素数量,避免频繁的内存重新分配。
- 使用
reserve
方法预留空间,进一步提高插入效率。 - 避免过度使用
std::copy
和std::back_inserter
,它们可能会导致性能问题。
通过掌握这些技巧,可以有效地将多个元素插入 STL 容器,并提升代码性能。