连接两个std :: vector


684

如何连接两个std::vectorS?


5
给出的答案实际上并不能串联。他们附加一个副本。(出于效率的考虑)可能会使用它来创建std :: vector串联方法,但是这需要对节点的管理进行一些复杂的共享,这可能就是为什么它没有完成的原因。
FauChristian '17

8
@FauChristian:不,从效率的角度来看可能没有用。向量存储器必须是连续的,因此建议的操作是不可能的。如果您希望“对节点的管理进行一些复杂的共享”,并且如果要以这种方式更改向量类,那么最终将导致双端队列。即使那样,以建议的方式重用内存也非常困难,尽管这将开始变得更加可行。我认为目前尚未实施。最主要的是,在这种共享管理节点(双端队列)的情况下,末端节点可能会部分为空。
Cookie

4
@lecaruyer您意识到自己刚刚标记了一个两年前重复问的问题
eshirima

9
我是唯一一个想知道为什么未在标准库中a + ba.concat(b)在标准库中实现的人吗?也许默认的实现方式不是最优的,但是每个数组的连接都不需要进行微优化
oseiskar,

9
多年的发展,是任何主流语言中最先进的运算符重载,一种使语言复杂度加倍的模板系统,但答案不是v = v1 + v2;而是
Spike0xff

Answers:


724
vector1.insert( vector1.end(), vector2.begin(), vector2.end() );

53
我只会添加代码以首先获取每个向量拥有的元素数量,并将vector1设置为拥有最大向量的元素。否则,您将进行许多不必要的复制。
Joe Pineda

34
我有个问题。如果vector1和vector2是相同的向量,是否可以使用?
亚历山大·拉弗蒂

6
如果您将多个向量串联在一起,那么先调用reserve目标向量是否有帮助?
Faheem Mitha 2012年

33
@AlexanderRafferty:仅当时vector1.capacity() >= 2 * vector1.size()。除非您致电,否则这是非典型的std::vector::reserve()。否则,矢量将重新分配,失效作为参数2和3通过迭代器
德鲁曼

28
太糟糕了,标准库中没有更简洁的表达式。.concat+=某物
nmr

193

如果您使用的是C ++ 11,并且希望移动元素而不是仅复制它们,则可以std::move_iterator与insert(或copy)一起使用:

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

int main(int argc, char** argv) {
  std::vector<int> dest{1,2,3,4,5};
  std::vector<int> src{6,7,8,9,10};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  // Print out concatenated vector.
  std::copy(
      dest.begin(),
      dest.end(),
      std::ostream_iterator<int>(std::cout, "\n")
    );

  return 0;
}

对于具有int的示例,这将不会更加高效,因为移动它们并不比复制它们更有效,但是对于具有优化移动的数据结构,它可以避免复制不必要的状态:

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

int main(int argc, char** argv) {
  std::vector<std::vector<int>> dest{{1,2,3,4,5}, {3,4}};
  std::vector<std::vector<int>> src{{6,7,8,9,10}};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  return 0;
}

移动之后,src的元素处于未定义但可以安全销毁的状态,并且其先前的元素最后直接转移到dest的新元素。


6
尝试连接std :: unique_ptr的std :: vector时,std :: make_move_iterator()方法对我有所帮助。
Knitschi 2014年

和之间有什么区别std::move(src.begin(), src.end(), back_inserter(dest))
kshenoy


77

或者您可以使用:

std::copy(source.begin(), source.end(), std::back_inserter(destination));

如果两个向量不包含完全相同的事物类型,则此模式很有用,因为您可以使用某些东西代替std :: back_inserter来将一种类型转换为另一种类型。


7
复制方法不是一个好方法。它将多次调用push_back,这意味着如果必须插入许多元素,则可能意味着多个重新分配。最好使用insert,因为向量实现可以做一些优化以避免重新分配。它可以在开始复制之前保留内存
Yogesh Arora

7
@Yogesh:可以,但是没有什么可以阻止您reserve先打电话。std::copy有时候有用的原因是如果您想使用以外的东西back_inserter
罗杰·利普斯科姆

当您说“多个分配”时,这是正确的-但是分配的数量是最坏的对数(添加的条目数)-这意味着添加条目的成本在添加的条目数中是恒定的。(基本上,除非配置文件显示您需要保留,否则不必担心)。
马丁·邦纳

您可能想使用std :: transform代替。
Martin Broadhurst,2016年

1
即使保留,副本也很烂。vector :: insert将避免所有检查:quick-bench.com/bLJO4OfkAzMcWia7Pa80ynwmAIA
Denis Yaroshevskiy

63

对于C ++ 11,我更喜欢以下将向量b附加到a:

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

ab不重叠时,b将不再使用。


这是std::move来自<algorithm>,而不是通常 std::move<utility>


10
如果a实际上是b,则为未定义的行为(如果您知道永远不会发生,则可以,但是值得在通用代码中了解)。
马丁·邦纳

1
@MartinBonner感谢您提到这一点。可能我应该回到insert更安全的旧方法。
德庆

15
啊,另一个std :: move。第一次看到它时,相当令人困惑。
xaxxon

1
insert()move_iterators 不同吗?如果是这样,怎么办?
GPhilo '18

1
我添加了一条关于std::move我们在这里所说的内容的注释,因为大多数人都不知道这种过载。希望这是一个进步。
YSC

38
std::vector<int> first;
std::vector<int> second;

first.insert(first.end(), second.begin(), second.end());

24

我更喜欢已经提到的一种:

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

但是,如果您使用C ++ 11,则还有另一种通用方法:

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

另外,这不是问题的一部分,但建议reserve在附加之前使用,以提高性能。而且,如果您将vector与自身连接在一起,而没有保留它会失败,那么您总是应该这样做reserve


所以基本上您需要什么:

template <typename T>
void Append(std::vector<T>& a, const std::vector<T>& b)
{
    a.reserve(a.size() + b.size());
    a.insert(a.end(), b.begin(), b.end());
}

2
std::通过依赖参数的查找来推导。end(a)就足够了。
阿苏

4
@Asu仅在from std::的类型a来自的情况下才会添加ADL std,这会破坏通用方面。
Potatoswatter

好点子。在这种情况下,它是一个向量,因此无论如何它都可以工作,但是是的,这是一个更好的解决方案。
阿苏

std :: begin()/ end()被添加到集合(如数组)中,而没有将它们作为成员函数。但是数组也没有insert()成员函数,并调用问题“是否有一个具有insert()但没有begin()的集合(该集合与std :: begin()一起使用?”)
James Curran '18


12

您应该使用vector :: insert

v1.insert(v1.end(), v2.begin(), v2.end());

1
这与汤姆·里特(Tom Ritter)和罗伯特·格兰伯(Robert Gamble)在2008年给出的答案不一样吗?
IgNite

9

连接的一般性能提升是检查向量的大小。并合并/插入较小的一个与较大的一个。

//vector<int> v1,v2;
if(v1.size()>v2.size()) {
    v1.insert(v1.end(),v2.begin(),v2.end());
} else {
    v2.insert(v2.end(),v1.begin(),v1.end());
}

如此简单,但我从来没有那样想过!
Zimano

2
示例代码不正确。v1.insert(v2.end()...正在使用迭代器v2来指定中的位置v1
大卫·斯通

您也可以使用快速交换。@DavidStone我编辑了它,以便可以更改concat的顺序。是否可以添加到向量的开头?
qwr

您可以插入开头,但这会比较慢。要真正“串联”,顺序通常很重要,因此您需要这样做。
大卫·斯通

7

如果希望能够简洁地连接向量,则可以重载+=运算符。

template <typename T>
std::vector<T>& operator +=(std::vector<T>& vector1, const std::vector<T>& vector2) {
    vector1.insert(vector1.end(), vector2.begin(), vector2.end());
    return vector1;
}

然后您可以这样称呼它:

vector1 += vector2;

6

如果您对强大的异常保证感兴趣(当复制构造函数可以引发异常时):

template<typename T>
inline void append_copy(std::vector<T>& v1, const std::vector<T>& v2)
{
    const auto orig_v1_size = v1.size();
    v1.reserve(orig_v1_size + v2.size());
    try
    {
        v1.insert(v1.end(), v2.begin(), v2.end());
    }
    catch(...)
    {
        v1.erase(v1.begin() + orig_v1_size, v1.end());
        throw;
    }
}

append_move如果向量元素的move构造函数可以抛出(通常是不可能的),则通常无法实现与强保证类似的行为。


也不能v1.erase(...扔吗?
类骨架

insert已经处理了。同样,对的调用erase等效于resize
Potatoswatter

我喜欢这个-谢谢!
natersoz

5

将此添加到您的头文件:

template <typename T> vector<T> concat(vector<T> &a, vector<T> &b) {
    vector<T> ret = vector<T>();
    copy(a.begin(), a.end(), back_inserter(ret));
    copy(b.begin(), b.end(), back_inserter(ret));
    return ret;
}

并以这种方式使用它:

vector<int> a = vector<int>();
vector<int> b = vector<int>();

a.push_back(1);
a.push_back(2);
b.push_back(62);

vector<int> r = concat(a, b);

r将包含[1,2,62]


不知道为什么这被否决了。这样做可能不是最有效的方法,但它没有错,而且很有效。
leeor_net

4

这是使用C ++ 11 move语义的通用解决方案:

template <typename T>
std::vector<T> concat(const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    if (lhs.empty()) return rhs;
    if (rhs.empty()) return lhs;
    std::vector<T> result {};
    result.reserve(lhs.size() + rhs.size());
    result.insert(result.cend(), lhs.cbegin(), lhs.cend());
    result.insert(result.cend(), rhs.cbegin(), rhs.cend());
    return result;
}

template <typename T>
std::vector<T> concat(std::vector<T>&& lhs, const std::vector<T>& rhs)
{
    lhs.insert(lhs.cend(), rhs.cbegin(), rhs.cend());
    return std::move(lhs);
}

template <typename T>
std::vector<T> concat(const std::vector<T>& lhs, std::vector<T>&& rhs)
{
    rhs.insert(rhs.cbegin(), lhs.cbegin(), lhs.cend());
    return std::move(rhs);
}

template <typename T>
std::vector<T> concat(std::vector<T>&& lhs, std::vector<T>&& rhs)
{
    if (lhs.empty()) return std::move(rhs);
    lhs.insert(lhs.cend(), std::make_move_iterator(rhs.begin()), std::make_move_iterator(rhs.end()));
    return std::move(lhs);
}

请注意,这与append到的区别vector


4

您可以为+运算符准备自己的模板:

template <typename T> 
inline T operator+(const T & a, const T & b)
{
    T res = a;
    res.insert(res.end(), b.begin(), b.end());
    return res;
}

接下来-使用+:

vector<int> a{1, 2, 3, 4};
vector<int> b{5, 6, 7, 8};
for (auto x: a + b)
    cout << x << " ";
cout << endl;

此示例提供了输出:

1 2 3 4 5 6 7 8

2
使用T operator+(const T & a, const T & b)是危险的,最好使用vector<T> operator+(const vector<T> & a, const vector<T> & b)
Matthieu H

4

C ++ 17提供了一种算法std::merge,该算法非常易于使用,

下面是示例:

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

int main()
{
    //DATA
    std::vector<int> v1{2,4,6,8};
    std::vector<int> v2{12,14,16,18};

    //MERGE
    std::vector<int> dst;
    std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(dst));

    //PRINT
    for(auto item:dst)
        std::cout<<item<<" ";

    return 0;
}

2
我认为使用std::vector::insert它并不比容易,但它的作用有所不同:将两个范围合并到一个新范围中,而不是在另一个结尾处插入一个向量。在答案中值得一提?
jb

4

如果您的目标只是为了只读而遍历值的范围,则另一种方法是将两个向量都包装在代理(O(1))周围,而不是复制它们(O(n)),这样就可以立即看到它们作为一个单一的,连续的。

std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };

VecProxy<int> AB(A, B);  // ----> O(1)!

for (size_t i = 0; i < AB.size(); i++)
    std::cout << AB[i] << " ";  // ----> 1 2 3 4 5 10 20 30

有关更多详细信息,请参见https://stackoverflow.com/a/55838758/2379625,包括“ VecProxy”实施以及优缺点。


3
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = {11, 12, 13, 14, 15};
copy(v2.begin(), v2.end(), back_inserter(v1));

6
尽管此代码段可以解决问题,但并未说明原因或答案。请为您的代码提供说明,因为这确实有助于提高您的帖子质量。举报人/审阅者: 对于仅限代码的答案,例如这一答案,请表决,不要删除!(注意:此答案实际上可能很容易解释,因此无需投票。您可能仍想添加解释,以防止出现更多的NAA / VLQ标志。)
Scott Weldon

2

我已经实现了此功能,该功能可以连接任意数量的容器,从右值引用移至其他位置进行复制

namespace internal {

// Implementation detail of Concatenate, appends to a pre-reserved vector, copying or moving if
// appropriate
template<typename Target, typename Head, typename... Tail>
void AppendNoReserve(Target* target, Head&& head, Tail&&... tail) {
    // Currently, require each homogenous inputs. If there is demand, we could probably implement a
    // version that outputs a vector whose value_type is the common_type of all the containers
    // passed to it, and call it ConvertingConcatenate.
    static_assert(
            std::is_same_v<
                    typename std::decay_t<Target>::value_type,
                    typename std::decay_t<Head>::value_type>,
            "Concatenate requires each container passed to it to have the same value_type");
    if constexpr (std::is_lvalue_reference_v<Head>) {
        std::copy(head.begin(), head.end(), std::back_inserter(*target));
    } else {
        std::move(head.begin(), head.end(), std::back_inserter(*target));
    }
    if constexpr (sizeof...(Tail) > 0) {
        AppendNoReserve(target, std::forward<Tail>(tail)...);
    }
}

template<typename Head, typename... Tail>
size_t TotalSize(const Head& head, const Tail&... tail) {
    if constexpr (sizeof...(Tail) > 0) {
        return head.size() + TotalSize(tail...);
    } else {
        return head.size();
    }
}

}  // namespace internal

/// Concatenate the provided containers into a single vector. Moves from rvalue references, copies
/// otherwise.
template<typename Head, typename... Tail>
auto Concatenate(Head&& head, Tail&&... tail) {
    size_t totalSize = internal::TotalSize(head, tail...);
    std::vector<typename std::decay_t<Head>::value_type> result;
    result.reserve(totalSize);
    internal::AppendNoReserve(&result, std::forward<Head>(head), std::forward<Tail>(tail)...);
    return result;
}

1

如果您正在寻找的是在创建后将向量附加到另一个向量的方法,那vector::insert是最好的选择,就像已经多次回答的那样,例如:

vector<int> first = {13};
const vector<int> second = {42};

first.insert(first.end(), second.cbegin(), second.cend());

遗憾的是,无法构造a const vector<int>,因为您必须先构造然后再构造insert


如果您实际上正在寻找的是容纳这两个vector<int>S 的串联的容器,那么在以下情况下,可能会有更好的选择:

  1. vector包含的原语
  2. 您所包含的原语大小为32位或更小
  3. 你想要一个const容器

如果上述全部正确,建议您使用basic_stringchar_type匹配您的中原始大小的匹配对象vector。您应static_assert在代码中包含,以验证这些大小保持一致:

static_assert(sizeof(char32_t) == sizeof(int));

有了这个事实,您可以执行以下操作:

const u32string concatenation = u32string(first.cbegin(), first.cend()) + u32string(second.cbegin(), second.cend());

有关之间差异的详细信息,string并且vector你可以看看这里:https://stackoverflow.com/a/35558008/2642059

有关此代码的实时示例,请参见此处:http : //ideone.com/7Iww3I


0

这个解决方案可能有点复杂,但是boost-range还提供了其他一些不错的东西。

#include <iostream>
#include <vector>
#include <boost/range/algorithm/copy.hpp>

int main(int, char**) {
    std::vector<int> a = { 1,2,3 };
    std::vector<int> b = { 4,5,6 };
    boost::copy(b, std::back_inserter(a));
    for (auto& iter : a) {
        std::cout << iter << " ";
    }
    return EXIT_SUCCESS;
}

通常,人们的意图是将向量组合在一起a并对其b进行迭代以执行一些操作。在这种情况下,存在可笑的简单join功能。

#include <iostream>
#include <vector>
#include <boost/range/join.hpp>
#include <boost/range/algorithm/copy.hpp>

int main(int, char**) {
    std::vector<int> a = { 1,2,3 };
    std::vector<int> b = { 4,5,6 };
    std::vector<int> c = { 7,8,9 };
    // Just creates an iterator
    for (auto& iter : boost::join(a, boost::join(b, c))) {
        std::cout << iter << " ";
    }
    std::cout << "\n";
    // Can also be used to create a copy
    std::vector<int> d;
    boost::copy(boost::join(a, boost::join(b, c)), std::back_inserter(d));
    for (auto& iter : d) {
        std::cout << iter << " ";
    }
    return EXIT_SUCCESS;
}

对于大向量,这可能是一个优势,因为没有复制。它也可以用于将概图轻松复制到多个容器中。

由于某种原因,没有类似的东西boost::join(a,b,c)是合理的。


0

您可以使用用于多态类型使用的模板,使用预先实现的STL算法来做到这一点。

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

template<typename T>

void concat(std::vector<T>& valuesa, std::vector<T>& valuesb){

     for_each(valuesb.begin(), valuesb.end(), [&](int value){ valuesa.push_back(value);});
}

int main()
{
    std::vector<int> values_p={1,2,3,4,5};
    std::vector<int> values_s={6,7};

   concat(values_p, values_s);

    for(auto& it : values_p){

        std::cout<<it<<std::endl;
    }

    return 0;
}

如果不想进一步使用第二个向量,则可以清除它(clear()方法)。


-3

老实说,您可以通过将两个向量中的元素复制到另一个向量中来快速连接两个向量,或者仅追加两个向量之一!这取决于您的目标。

方法1:分配新向量,其大小是两个原始向量大小的总和。

vector<int> concat_vector = vector<int>();
concat_vector.setcapacity(vector_A.size() + vector_B.size());
// Loop for copy elements in two vectors into concat_vector

方法2:通过添加/插入向量B的元素来附加向量A。

// Loop for insert elements of vector_B into vector_A with insert() 
function: vector_A.insert(vector_A .end(), vector_B.cbegin(), vector_B.cend());

4
您的答案添加了其他答案中没有提供的内容?
2016年

13
@Mat:粗体字符。
marcv81

如果此后不再需要原始向量,则最好使用它,std::move_iterator以便移动元素而不是复制元素。(请参见en.cppreference.com/w/cpp/iterator/move_iterator)。
tmlen

什么setcapacity啊 什么function:
LF

@LF我想他在说resize方法。
Matthieu H
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.