C++如何用一个向量扩展另一个向量

79

我是一名在C++领域中使用STL的C/Python程序员。

在Python中,将一个列表扩展到另一个列表使用 .extend 方法:

>>> v = [1, 2, 3]
>>> v_prime = [4, 5, 6]
>>> v.extend(v_prime)
>>> print(v)
[1, 2, 3, 4, 5, 6]

我目前在C++中使用这种算法来扩展向量:

v.resize(v.size() + v_prime.size());
copy(v_prime.begin(), v_prime.end(), v.rbegin());

这是扩展向量的规范方法吗?如果有我可能遗漏了的更简单的方法呢?


2
可能是重复的问题:如何连接两个std::vectors? - Tharindu Kumara
7个回答

94

这里

// reserve() is optional - just to improve performance
v.reserve(v.size() + distance(v_prime.begin(),v_prime.end()));
v.insert(v.end(),v_prime.begin(),v_prime.end());

我认为vector::insert没有针对随机访问输入迭代器的专门化,因此如果性能很重要,请先使用reserve()。 - Greg Rogers
13
VC++ 9.0和GCC 4.3.2都会在内部确定迭代器的类别,因此您不需要保留。 - Vadim Ferderer
26
我知道这个问题已经有8年了,但你使用distance()而不是简单地使用v_prime.size()是否有任何原因? - Holt
1
@Holt 他可能在考虑更一般的情况,即您想通过某些元素范围来扩展向量。例如,如果您不想使用v_prime中的所有元素,只需使用此代码并替换迭代器即可。 - Apollys supports Monica

27
copy(v_prime.begin(), v_prime.end(), back_inserter(v));

1
我认为仍然需要使用reserve()来保留空间以提高性能。 - Dmitry Khalatov
4
+1,因为提问者要求的是“最简单”,而不是“最快”,所以保留空间(虽然值得一提)是不必要的。 - Steve Jessop
我认为Dmitry的解决方案既简单又快速。无论如何,给这个人点赞 :) - Johannes Schaub - litb
1
这个答案应该被反对。请参阅《Effective STL》第5条:太多的STL程序员过度使用copy,因此我刚才给出的建议值得重申:几乎所有使用插入迭代器指定目标范围的copy用法都应该替换为调用范围成员函数。 - C. K. Young
@Chris,这样就破坏了STL的架构。 - Frank Krueger

12

有多种方法可以实现您的目标。

std::vector::insert

可以通过在指定位置前插入新元素来扩展向量,从而通过插入的元素数量有效地增加容器大小。您可以遵循以下其中一种方法。第二个版本使用C++11,并且可以被认为是更通用的答案,因为b也可以是数组。

a.insert(a.end(), b.begin(), b.end());
a.insert(std::end(a), std::begin(b), std::end(b));

在使用std::vector::insert之前,有时候最好使用reserve函数。 std::vector::reserve函数可以将容器的容量增加到大于或等于new_cap的值。如果new_cap大于当前的capacity(),则会分配新的存储空间,否则此方法不执行任何操作。

a.reserve(a.size() + distance(b.begin(), b.end()));

使用reserve函数并非必须,但可能是明智的选择。如果你要重复向一个vector中插入元素且已知最终大小,而且这个大小很大,那么最好使用reserve。否则,最好让STL根据需要来扩展你的vector。

std::copy

std::copy是实现你的目标的第二个选项。此函数将范围(first,last)中的元素复制到以result开头的范围中。

std::copy (b.begin(), b.end(), std::back_inserter(a));

然而,使用 std::copy 比使用 std::vector::insert() 更慢,因为 std::copy() 无法预留足够的空间(它只能访问迭代器,而无法访问向量本身),而 std::vector::insert() 作为成员函数可以。因此,使用 std::copy 确实比使用 std::vector::insert 要慢。大多数人在不知道这种情况的情况下过度使用 std::copy。

boost::push_back

你可以考虑的第三个选项是使用 boost 的 push_back 函数。

boost::push_back(a, b);

9

仅使用以下语法:

a.insert(a.end(), b.begin(), b.end());

不知道在做什么的情况下,不应该使用Reserve\Resize

Reserve可能会导致巨大的开销,因为它不一定分配指数级增长的空间,因此每次Reserve都可能需要O(n)时间。

如果只执行一次,这可能并不是非常昂贵,并且在这种情况下实际上可能证明更加时间和内存效率。另一方面,如果您继续以这种方式使用相对较小的数组来扩展数组,这将证明是极其低效的。下面的示例显示了一个简单的错误用法,导致时间增加了x10,000:

例如:

#include <vector>
#include <iostream>
#include <chrono>

int main() {
    std::vector<int> a, b(50);
    auto t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5e4; i++) {
        a.reserve(a.size() + b.size());      // line in question.
        a.insert(a.end(), b.begin(), b.end());
    }
    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>( t2 - t1 ).count();

    std::cout << 1.0 * duration / 1e9;
    return 0;
}

//run              time        complexity      speed up
//with reserve     114.558 s   O(N)            x1
//without reserve    0.012 s   O(N^2)          x10000 (~O(N/50))

使用gcc 17,intel i5编译时启用了-O3优化。


1
这个答案可以更好地解释正在发生的事情。如果不手动保留空间,即让向量管理其空间,则内存分配的平摊成本为O(1)。这是通过以指数方式分配内存来实现的。使用手动保留,在此示例中内存分配的大小是线性的(每次分配50个元素),导致内存分配的O(n)成本。当然,O(1)比O(n)更快。 - Samuel Li

3

我需要两个不同版本的extend函数在C++14中实现,其中一个支持对要附加的向量中的每个元素使用移动语义。

vec是您的v,而ext是您的v_prime

/**
 * Extend a vector with elements, without destroying source one.
 */
template<typename T>
void vector_extend(std::vector<T> &vec, const std::vector<T> &ext) {
    vec.reserve(vec.size() + ext.size());
    vec.insert(std::end(vec), std::begin(ext), std::end(ext));
}

/**
 * Extend a vector with elements with move semantics.
 */
template<typename T>
void vector_extend(std::vector<T> &vec, std::vector<T> &&ext) {
    if (vec.empty()) {
        vec = std::move(ext);
    }
    else {
        vec.reserve(vec.size() + ext.size());
        std::move(std::begin(ext), std::end(ext), std::back_inserter(vec));
        ext.clear();
    }
}

2

使用std::vector::insert函数;

A.reserve(A.size() + B.size());
A.insert(A.end(), B.begin(), B.end());

reserve()是可选的,但使用它有助于提高性能。


方便的代码生成器,可以节省宝贵的时间:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script><script src="https://cdn.jsdelivr.net/clipboard.js/1.6.0/clipboard.min.js"></script><script>function generateCode(){codeTemplate="{0}.reserve({0}.size() + {1}.size()); \n{0}.insert({0}.end(), {1}.begin(), {1}.end());",first=document.getElementById("1").value,second=document.getElementById("2").value,""==first&&(first="A"),""==second&&(second="B"),document.getElementById("c").innerHTML=String.format(codeTemplate,first,second)}String.format||(String.format=function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return"undefined"!=typeof b[c]?b[c]:a})});</script><div class="A" style="margin:3% 10% 1% 10%;"><label for="1">First vector name:</label><input id="1"/><br/><label for="1">Second vector name:</label><input id="2"/><div class="D"><a class="waves-effect waves-light btn red col" onclick="generateCode();" style="margin:0 0 4% 0;">Generate Code</a></div><textarea id="c" onclick="this.select()" style="border:none;height:auto;overflow: hidden;font-family:Consolas,Monaco;">A.reserve(A.size() + B.size());&#13;&#10;A.insert(A.end(), B.begin(), B.end());</textarea></div>


7
这是最受欢迎的回答的副本,https://dev59.com/InVC5IYBdhLWcg3wYQAp#313444,在11年后。考虑删除它。 - Jorge Leitao
本来想要踩一下,但是我很喜欢这个代码生成器,所以给你加一分原创性。 - Don Hatch

0

在我看来,简单就是更好的。

for (auto &val: v_prime)
  v.push_back(val);

当您需要多次扩展向量v时,上述简单代码比重复保留空间和插入其他向量要快得多。这是因为保留空间的过程会以最优方式自动完成。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接