当前位置:   article > 正文

【STL】Vector和List的深拷贝浅拷贝问题_vector push_back深拷贝吗?

vector push_back深拷贝吗?

STL容器共性机制

STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素在另行拷贝一份放入到容器中,而不是将原数据直接放入到容器中,也就是说我们提供的元素必须能够被拷贝(自己写的指针的话,就需要自己写一个拷贝构造函数;如果是一个类,会调用拷贝构造函数※)

    1.除了Queue和Stack之外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素

    2.通常STL不会抛出异常,需要使用传入正确的参数

    3.每个容器都提供了一个默认的构造函数和默认的拷贝构造函数

    4.大小相关的构造方法:1.size()返回容器中元素的个数  2.empty()判断容器是否为空。

Vector的push_back问题:

如果发生扩容,会拷贝就元素到扩容后的里面,然后析构原有元素

参考:STL-vector

此时如果有浅拷贝发生,会出现析构问题!

流程:

https://blog.csdn.net/u012501459/article/details/44132147

https://zohead.com/archives/vector-push-back-space-copy/

  1. #include <iostream>
  2. #include <vector>
  3. #include <list>
  4. using namespace std;
  5. class Base {
  6. public:
  7. Base(vector<int> vec,list<int> list):m_vec(vec),m_list(list){};
  8. ~Base(){
  9. cout<<"destroy class"<<endl;
  10. };
  11. Base(const Base& base)
  12. {
  13. m_list = base.m_list;
  14. m_vec = base.m_vec;
  15. cout<<"copy class"<<endl;
  16. };
  17. private:
  18. vector<int> m_vec;
  19. list<int> m_list;
  20. };
  21. int main()
  22. {
  23. vector<int> vec1 (5,0);
  24. list<int> list1(3,1);
  25. Base base1(vec1,list1);
  26. Base base2(vec1,list1);
  27. cout<<"push_back list:"<<endl;
  28. list<Base> m_listGroup;
  29. m_listGroup.push_back(base1);
  30. m_listGroup.push_back(base2);
  31. cout<<"push_back vector:"<<endl;
  32. vector<Base> m_vectGroup;
  33. m_vectGroup.push_back(base1);
  34. m_vectGroup.push_back(base2);
  35. cout<<"end test"<<endl;
  36. }

push_back list:
copy class
copy class
push_back vector:
copy class
copy class
copy class
destroy class
end test
destroy class
destroy class
destroy class
destroy class
destroy class
destroy class

结果分析

vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象,这会调用 sss 的拷贝构造函数,第一次的 copy 正常,而且 vector 的实际容量也由 0  变为 1。

第二次调用 push_back,通过输出会发现调用了两次拷贝构造函数,一次析构函数,原来 vector 此时判断容量不够,将容量扩大为原来的两倍,变为 2,并将原来的元素再次拷贝一份存放到新的内存空间,然后拷贝新加的类对象,最后再释放原来的元素。

第三次调用 push_back 时,vector 自动扩大为4,因此拷贝构造函数调用了3次,析构函数调用了2次,程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。

 

1.拷贝本身是深拷贝

  1. #include <vector>
  2. #include <iostream>
  3. using namespace std;
  4. typedef struct point{
  5. int x;
  6. int y;
  7. }Point;
  8. ostream& operator<<(ostream& output, const Point &a)
  9. {
  10. return output << a.x <<" "<< a.y;
  11. }
  12. int main(){
  13. Point * a = new Point;
  14. vector<Point> PointList;
  15. a->x = 3;
  16. a->y = 4;
  17. PointList.push_back(*a);
  18. a->x = 4;
  19. a->y = 4;
  20. PointList.push_back(*a);
  21. a->x = 5;
  22. a->y = 4;
  23. PointList.push_back(*a);
  24. delete a;
  25. for (vector<Point>::iterator i = PointList.begin(); i != PointList.end(); i++){
  26. cout << (*i)<< endl;
  27. }
  28. return 0;
  29. }

这说明完成的不是将a的地址加入到vector,而是将数据整个拷贝了一份加入进去

2.如果用类(如Point类)构造的容器来说如果有new/malloc分配的空间,要重写复制构造函数才不会出问题。(因为默认复制构造函数为浅拷贝)

  1. #include<iostream>
  2. #include<vector>
  3. using namespace std;
  4. //深拷贝和浅拷贝的问题
  5. #if 0
  6. class Person
  7. {
  8. public:
  9. Person(const char* name,int age)
  10. {
  11. this->pName = new char[strlen(name) + 1]; //开辟内存
  12. strcpy(this->pName, name); //值拷贝
  13. this->pAge = age;
  14. }
  15. //只写上面的会出现程序宕掉,原因是出现了两次析构
  16. //解决办法,写一个拷贝构造
  17. Person(const Person& p)
  18. {
  19. this->pName = new char[strlen(p.pName) + 1];
  20. strcpy(this->pName, p.pName);
  21. this->pAge = p.pAge;
  22. }
  23. //重载等号
  24. Person& operator=(const Person& p)
  25. {
  26. //先释放空间,然后在拷贝
  27. if (this->pName != NULL)
  28. {
  29. delete[] this->pName;
  30. }
  31. this->pName = new char[strlen(p.pName) + 1];
  32. strcpy(this->pName, p.pName);
  33. this->pAge = p.pAge;
  34. return *this;
  35. }
  36. ~Person()
  37. {
  38. if (this->pName != NULL)
  39. {
  40. delete[] this->pName;
  41. }
  42. }
  43. public:
  44. char* pName; //指针
  45. int pAge;
  46. };
  47. void test01()
  48. {
  49. Person p("aaa", 21);
  50. vector<Person> vPerson;
  51. vPerson.push_back(p);
  52. }
  53. #endif
  54. class Person2
  55. {
  56. public:
  57. Person2(char* s)
  58. {
  59. pStr = s;
  60. }
  61. Person2() {}
  62. /*Person2& operator=(const Person2 p)
  63. {
  64. pStr = p.pStr;
  65. return *this;
  66. }*/
  67. Person2& operator=(const Person2& p)
  68. {
  69. if (strlen(pStr) != strlen(p.pStr))
  70. pStr = new char[strlen(p.pStr) + 1]; //为被赋值对象申请了一个新的内存
  71. /*if (*this != p)
  72. strcmp(p.pStr, p.pStr);*/
  73. return *this;
  74. }
  75. public:
  76. char* pStr;
  77. };
  78. void test02()
  79. {
  80. Person2 p2("aaa"),p3; //上面不加const这里报错,加了const后上面的=报错
  81. p3 = p2;
  82. cout << p2.pStr << endl;
  83. cout << p3.pStr << endl;
  84. }
  85. int main()
  86. {
  87. //test01();
  88. test02();
  89. system("pause");
  90. return 0;
  91. }

作为函数参数和返回值

读取vector内的元素,如果赋值给其他变量,是将对象复制一份新的。

Item ii  = list[0];

如果直接操作数组元素,是不会产生对象复制的

list[0].a 

1.std::vector 作为参数传入

是值传递,vector本身,及vector内的所有元素都会复制一遍。

得不偿失,可以使用引用传递。

c++中常用的vector容器作为参数时,有三种传参方式,分别如下(为说明问题,用二维vector):

  • function1(std::vector<std::vector<int> > vec),传值
  • function2(std::vector<std::vector<int> >& vec),传引用
  • function3(std::vector<std::vector<int> >* vec),传指针

注意,三种方式分别有对应的const形式,不在此讨论。

三种方式对应的调用形式分别为:

  • function1(vec),传入值
  • function2(vec),传入引用
  • function3(&vec),传入地址

三种方式的效果分别为:

  • 会发生拷贝构造
  • 不会发生拷贝构造
  • 不会发生拷贝构造

2.std::vector作为函数返回值

不会创建新vector对象的。函数内返回的跟接收返回值的是一个对象。

读取vector内的元素,如果赋值给其他变量,是将对象复制一份新的。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <vector>
  4. class Item{
  5. public:
  6. int a;
  7. int b;
  8. };
  9. std::vector<Item> vectorTestFunc(std::vector<Item> input){
  10. printf("vectorTestFunc >>> in %p %p, %p\n",&input, &input[0], &input[0].a);
  11. Item item = input[0];
  12. std::vector<Item> output;
  13. output.push_back(item);
  14. printf("vectorTestFunc <<< in %p %p, %p\n",&output, &output[0], &output[0].a);
  15. return output;
  16. }
  17. int main(int argc, char* argv[]){
  18. std::vector<Item> list;
  19. Item i;
  20. i.a = 1;
  21. i.b =2;
  22. printf("i adr is %p, %p\n",&i, &i.a);
  23. list.push_back(i);
  24. printf("list[0] adr is %p, %p\n",&list[0], &list[0].a);
  25. Item ii = list[0];
  26. printf(" ii adr is %p, %p\n",&ii, &ii.a);
  27. printf("vectorTestFunc in %p %p, %p\n",&list, &list[0], &list[0].a);
  28. std::vector<Item> output = vectorTestFunc(list);
  29. printf("vectorTestFunc output %p %p, %p\n",&output, &output[0], &output[0].a);
  30. return 0;
  31. }

build$ make; ./main
Scanning dependencies of target main
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
i adr is 0x7ffc5a16b160, 0x7ffc5a16b160
list[0] adr is 0x5583c3b0f280, 0x5583c3b0f280
 ii  adr is 0x7ffc5a16b168, 0x7ffc5a16b168
vectorTestFunc in  0x7ffc5a16b170 0x5583c3b0f280, 0x5583c3b0f280
vectorTestFunc >>> in 0x7ffc5a16b1b0   0x5583c3b0f2a0, 0x5583c3b0f2a0
vectorTestFunc <<< in 0x7ffc5a16b190    0x5583c3b0f2c0, 0x5583c3b0f2c0
vectorTestFunc output  0x7ffc5a16b190 0x5583c3b0f2c0, 0x5583c3b0f2c0

如果把函数参数改成引用:

  1. build$ make; ./main
  2. Scanning dependencies of target main
  3. [ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
  4. [100%] Linking CXX executable main
  5. [100%] Built target main
  6. i adr is 0x7ffe81b1cf40, 0x7ffe81b1cf40
  7. list[0] adr is 0x5641c6771280, 0x5641c6771280
  8.  ii  adr is 0x7ffe81b1cf48, 0x7ffe81b1cf48
  9. vectorTestFunc in  0x7ffe81b1cf50 0x5641c6771280, 0x5641c6771280
  10. vectorTestFunc >>> in 0x7ffe81b1cf50   0x5641c6771280, 0x5641c6771280
  11. vectorTestFunc <<< in 0x7ffe81b1cf70    0x5641c67712a0, 0x5641c67712a0
  12. vectorTestFunc output  0x7ffe81b1cf70 0x5641c67712a0, 0x5641c67712a0

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/452783
推荐阅读
相关标签
  

闽ICP备14008679号