【问题标题】:Order of inserting elements into a 2D Vector in c++在c ++中将元素插入二维向量的顺序
【发布时间】:2016-08-05 00:21:55
【问题描述】:

我在练习 C++ 向量时,在将元素插入 2D 向量时发现了一个问题。在以下示例中:

#include <iostream>
#include <vector>

void fillVector(std::vector<std::vector<int> > &vec) {
    std::vector<int> list1;
    vec.push_back(list1);
    list1.push_back(1);
    list1.push_back(2);

    std::vector<int> list2;
    list2.push_back(3);
    list2.push_back(4);
    vec.push_back(list2);

    return;
}

int main() {
    std::vector<std::vector<int> > vect;
    fillVector(vect);

    std::cout << "vect size: " << vect.size() << std::endl;

    for(int i = 0; i < vect.size(); i++) {
        std::cout << "vect in size: " << vect.at(i).size() << std::endl;
    }
}

第一个inner-list的大小为0,第二个inner-list的大小为2。list1list2的唯一区别是list1先插入vec 2D 向量,在元素被插入之前,而元素首先被插入到list2,然后它本身被插入到 2D 向量中。函数返回后,插入list1的元素不打印,大小不变。

我也尝试了第一种方法,而不是指针,

std::vector<int> *list3 = new std::vector<int>();
vec.push_back(*list3);
list3->push_back(5);
list3->push_back(6);

但是,从调用函数读取时 list3 的大小仍然为 0。 我不明白这两种方法之间的区别。为什么插入元素后必须追加列表?

【问题讨论】:

  • 在第一种情况下,您需要将list1.push_back(1); list1.push_back(2); 更改为vec.back().push_back(1); vec.back().push_back(2); 否则您只需将项目添加到list1 而不是vec
  • 顺便说一句,您的第三个解决方案(指针)基本上不起作用,原因与第一个案例不起作用的原因相同,因为您 push_back() 取消引用 list3(只是 list3 的值) .
  • 扩展@DimChtz 评论:当您将某些内容放入vector 时,vector 会存储一个副本。操作原件不会影响副本。

标签: c++ pointers vector


【解决方案1】:

您似乎在期待类似 python 的行为?无论如何,在 C++ 中,引用、指针和值之间的区别非常重要。

你的fillVector 函数有正确的想法,因为它引用了一个二维向量std::vector&lt;std::vector&lt;int&gt; &gt; &amp;vec - 注意&amp;。但是,当您创建 list1 并立即使用 push_back()

std::vector<int> list1;
vec.push_back(list1);

您正在推动空向量。 push_back() 将创建此向量的副本,该向量将包含在vect 中(在main 中),并且是与list1 完全分开的向量。

此时如果要访问已经推送的向量,可以使用back(),它返回对向量vec中最后一个元素的引用,也就是最后推送的那个。

vec.back().push_back(1);
vec.back().push_back(2);

list2你在推回之前修改,所以当复制时,它是由已经修改的向量组成的。您对list3 的尝试并没有真正改变太多,您在push_back() 时取消引用指针并且复制都是一样的。您可以将 vect 设为 std::vector&lt;std::vector&lt;int&gt;*&gt;,但我强烈建议您不要这样做,因为您必须手动管理内存 - 使用 new

注意:虽然在某些时候学习对您很重要,但您应该尽可能避免使用指针,特别是 RAW 指针(请查看 smart pointers)。 std::vector,以及我所知道的所有其他 std 容器,都进行自己的内存管理——他们肯定会比你更有效,而且没有 BUG。


我建议您只处理最后推送的向量,如下所示:

void fillVector(std::vector<std::vector<int> > &vec) {
    vec.push_back(std::vector<int>());
    vec.back().push_back(1);
    vec.back().push_back(2);

    vec.push_back(std::vector<int>());
    vec.back().push_back(3);
    vec.back().push_back(4);
    return;
}

如您所见,几乎相同的代码重复了两次,因此您可以轻松循环以获得此结果或其他结果。

【讨论】:

    【解决方案2】:

    vector.push_back(var) 复制var 并将其插入向量中。如果您在空列表上使用push_back(),它会将空列表复制到向量中。在此之后更改列表中的值不会影响插入到向量中的副本。这是因为您将实际对象传递给push_back(),而不是指向对象的指针。

    在第三个示例中,您朝着正确的方向迈出了一步,但在传递列表之前取消了对列表的引用,因此push_back() 复制了该地址的内容。

    解决此问题的一个简单方法是在将列表插入向量之前始终设置值。

    如果您希望能够在插入列表后更改值,请使用vect.at(i).push_back(val) 在 i 处向列表添加值。 您还可以使向量包含指向其他向量的指针,而不是向量本身:

    void fillVector(std::vector<std::vector<int> *> &vec) {
        std::vector<int> *list1 = new std::vector<int>(); //Remember to allocate memory since we're using pointers now
        list1->push_back(1);
        list1->push_back(2); 
        vec.push_back(list1); // Copy the pointer that is list1 into vec
    
        std::vector<int> *list2 = new std::vector<int>();
        vec.push_back(list2); // Copy the pointer that is list2 into vec
        list2->push_back(3);
        list2->push_back(4);
        return;
    }
    
    int main() {
        std::vector<std::vector<int> *> vect; // Vector of pointers to vectors
        fillVector(vect);
    
        std::cout << "vect size: " << vect.size() << std::endl;
    
        for(int i = 0; i < vect.size(); i++) {
            std::cout << "vect in size: " << vect.at(i)->size() << std::endl;
        }
    }
    std::vector<std::vector<int> *> vec = new; // Vector of pointers
    

    【讨论】:

    • 谢谢你解释得这么好。我喜欢使用指针的解决方案,在这种情况下它们似乎更安全。
    • 别担心,完成后记得打电话给delete,因为他们分配给new
    • @iamseiko 他们不是。如果您没有准备好,vectors 中的指针正在邀请您进入地狱世界。
    • 虽然它确实有效,但我强烈建议您避免使用此解决方案,因为它需要您进行手动内存管理 - 使用 new,这是一个非常大的禁忌。跨度>
    • 如果你使用的是c++11,你可以使用智能指针(例如std::unique_ptr)来避免需要小心删除,但是直接对向量元素进行操作要简单得多.
    【解决方案3】:

    当您将某些内容放入std::vector 时,vector 会存储一个副本。操作原件不会影响副本。如果将指针放入指针的vector 中,vector 仍会存储指针的副本。 vector 中的原始和副本都指向同一个内存,因此您可以操作引用的数据并查看引用数据的变化。

    所以...

    std::vector<int> list1;
    vec.push_back(list1);
    list1.push_back(1);
    list1.push_back(2);
    

    将空list1 的副本放入vec。然后将 1 和 2 的副本放入原始的list1vec 中的list1 副本不受影响。

    写成

    std::vector<int> list1;
    vec.push_back(list1);
    vec.back().push_back(1);
    vec.back().push_back(2);
    

    将纠正这一点。和稍微干净一点的版本一样

    vec.push_back(std::vector<int>());
    vec.back().push_back(1);
    vec.back().push_back(2);
    

    因为它没有浪费list1 到处乱逛,弄乱了范围。

    vec.push_back(std::vector<int>{1,2});
    

    如果您的编译器支持 C++11 或更高版本,将会进一步简化。

    另一方面...

    std::vector<int> list2;
    list2.push_back(3);
    list2.push_back(4);
    vec.push_back(list2);
    

    将 3 和 4 的副本放入 list2,然后放入 list2 的副本,以及 3 和 4 的副本。

    同上,

    std::vector<int> list2{3,4};
    vec.push_back(list2);
    

    可以减少工作量。

    很遗憾,您对list3 的实验失败了,因为list3 是一个指针,vec 不包含指针,所以list3 被取消引用,而引用的vector 被复制。没有存储指向数据list3 引用的指针,并且vec 包含一个空向量,原因同上。

    std::vector<int> *list3 = new std::vector<int>();
    vec.push_back(*list3);
    list3->push_back(5);
    list3->push_back(6);
    

    关于在vectors 中存储指针的几点说明

    1. vector 仅存储指针的副本。指向的数据必须以这样的方式限定范围,即在 vector 完成之前它不会被破坏。一种解决方案是动态分配存储空间。
    2. (这通常适用于动态分配的指针)如果您动态分配,迟早有人必须清理混乱和delete 这些指针。查看storing smart pointers 而不是原始指针,根本不存储指针。
    3. 熟悉the Rule of Threevector 会照顾自己,但如果您有两个 vector 副本,并且您只从其中一个中删除和删除一个指针,那么您将需要进行一些调试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-05
      • 2021-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多