当前位置:   article > 正文

【C++】-- String深浅拷贝详解_c++ string 拷贝

c++ string 拷贝

目录

一、浅拷贝和深拷贝定义

1.浅拷贝原理

2.深拷贝原理

二、浅拷贝和深拷贝实现

1.浅拷贝实现 

 2.深拷贝实现

(1)为什么引用类型成员使用浅拷贝不能实现拷贝构造 

(2)如何实现深拷贝 


一、浅拷贝和深拷贝定义

拷贝对象时,需要创建相同的字节序、类型、和资源。

1.浅拷贝原理

创建一个新对象, 来接收要重新复制或引用的对象值,要求该对象的所有成员变量全部都不在堆上分配空间。假如果对象的成员变量全部都是内置类型,复制的就是地址;如果对象的成员变量有引用数据类型,复制的就是内存中的地址。对其中一个对象的修改都会影响到另一个对象。

2.深拷贝原理

深拷贝将一个对象完整地从内存中拷贝出来给新对象,从堆中开辟新空间存放新对象。对新对象的修改不会改变原对象,实现两个对象的分离。

二、浅拷贝和深拷贝实现

1.浅拷贝实现 

当一个类对象的所有成员变量全部都是内置类型时,可以使用浅拷贝完成拷贝构造:

(1)显式定义拷贝构造函数完成浅拷贝;

(2)如果不显式定义拷贝构造函数,编译器会自动生成默认拷贝构造函数来完成浅拷贝。

如日期类的所有成员变量全部都是内置类型:

  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6. //构造函数
  7. Date(int year = 2022, int month = 4, int day = 8)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. void Print()
  14. {
  15. cout << _year << "-" << _month << "-" << _day << endl;
  16. }
  17. //析构函数:清理资源
  18. ~Date()
  19. {
  20. cout << "~Date()" << endl;//在析构函数内打印
  21. }
  22. private:
  23. int _year;
  24. int _month;
  25. int _day;
  26. };
  27. int main()
  28. {
  29. Date d1(2022, 9, 6);//调用构造函数
  30. Date d4(d1);
  31. d1.Print();
  32. d4.Print();
  33. return 0;
  34. }

在没有显式定义拷贝构造函数的情况下, d4构造成功了:

 2.深拷贝实现

(1)为什么引用类型成员使用浅拷贝不能实现拷贝构造 

对于引用类型的成员变量,如果在堆上开辟空间,不显式定义拷贝构造函数的话,会引发两个问题:

①调用析构函数时,这块空间被free了两次

②对其中一个对象进行修改,都会导致另外一个对象被修改

对于stack类,它的成员变量_a是在堆上开辟空间的,如果不显式定义拷贝构造函数,那么会引发程序崩溃:

  1. #include <stdlib.h>
  2. #include <iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. //构造函数
  9. Stack(int capacity = 4)
  10. {
  11. _a = (STDataType*)malloc(sizeof(STDataType) * 4);
  12. _size = 0;
  13. _capacity = capacity;
  14. }
  15. //析构函数:清理资源
  16. ~Stack()
  17. {
  18. free(_a);
  19. _a = nullptr;
  20. _size = _capacity = 0;
  21. }
  22. private:
  23. STDataType* _a;
  24. int _size;
  25. int _capacity;
  26. };
  27. int main()
  28. {
  29. Stack st1;
  30. Stack st2(st1);
  31. return 0;
  32. }

 这是因为调构造s1对象时,_a指向了堆上开辟的空间,由于没有显式定义拷贝构造函数,因此对象st2的成员变量_a拷贝的是st1的成员变量_a指针,即把st1的_a指针的值,拷贝给了st2的_a,那么两个指针的值是一样的,st1的_a和st2的_a指向同一块空间:

造成程序崩溃的原因:调用析构函数,这块空间被free了两次:后定义的先析构,st2先析构,free(_a)就把这块空间释放了,这块空间就被归还给了操作系统,再把_a置空了。再析构st1时,free(_a)还要释放这块空间,同一块空间被释放了两次。

另外,由于共用同一块空间,st1和st2无论谁被修改,都会导致对方也被修改。

(2)如何实现深拷贝 

①stack类使用深拷贝来拷贝构造对象:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <stdlib.h>
  3. #include <iostream>
  4. using namespace std;
  5. typedef int STDataType;
  6. class Stack
  7. {
  8. public:
  9. //构造函数
  10. Stack(int capacity = 4)
  11. {
  12. _a = (STDataType*)malloc(sizeof(STDataType) * 4);
  13. _size = 0;
  14. _capacity = capacity;
  15. }
  16. //拷贝构造函数
  17. Stack(const Stack& s)
  18. :_a(new STDataType[s._capacity])
  19. , _size(s._size)
  20. , _capacity(s._capacity)
  21. {
  22. }
  23. //析构函数:清理资源
  24. ~Stack()
  25. {
  26. free(_a);
  27. _a = nullptr;
  28. _size = _capacity = 0;
  29. }
  30. private:
  31. STDataType* _a;
  32. int _size;
  33. int _capacity;
  34. };
  35. int main()
  36. {
  37. Stack st1;
  38. Stack st2(st1);
  39. return 0;
  40. }

st1和st2地址不一样,实现了深拷贝: 

②string类使用深拷贝来拷贝构造对象:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<iostream>
  3. using namespace std;
  4. namespace delia
  5. {
  6. class string
  7. {
  8. public:
  9. //构造函数
  10. string(const char* str = "")
  11. {
  12. _str = new char[strlen(str) + 1];
  13. strcpy(_str, str);
  14. }
  15. //传统的拷贝构造函数
  16. string(const string& s)
  17. :_str(new char[strlen(s._str) + 1])
  18. {
  19. strcpy(_str, s._str);
  20. }
  21. char& operator[](size_t i)
  22. {
  23. return _str[i];
  24. }
  25. size_t size()
  26. {
  27. return strlen(_str);
  28. }
  29. const char* c_str()
  30. {
  31. return _str;
  32. }
  33. //析构函数
  34. ~string()
  35. {
  36. delete[] _str;
  37. _str = nullptr;
  38. }
  39. private:
  40. char* _str;
  41. };
  42. }
  43. int main()
  44. {
  45. delia::string s1("hello world");
  46. delia::string s2(s1);
  47. return 0;
  48. }

F10-监视,可以看到s1._str和_s2._str的地址不同,各自拥有各自的空间,实现了深拷贝:

上面实现的是传统的拷贝构造,还有一种现代拷贝构造:

  1. //现代的拷贝构造
  2. string(const string& s)
  3. :_str(nullptr)
  4. {
  5. string tmp(s._str);
  6. swap(_str, tmp._str);
  7. }

监视发现,s1._str和s2._str地址不同,内容相同,实现了深拷贝:

现代拷贝构造做的事:

(1)将成员初始化成空指针

(2)用原对象成员构造临时对象

(3)交换临时对象和原对象成员

(4)出了拷贝构造函数会自动调用析构函数释放临时对象空间

 _str必须在初始化列表赋值成空指针的原因:构造tmp对象时使用s._str初始化,执行swap(_str, tmp._str);来交换this._str和tmp._str的内容,交换完毕后,tmp对象的成员内容为空指针,tmp出了拷贝构造函数作用域就会调用析构函数,会把tmp在堆上申请的空间释放掉,如果_str没有被赋值成空指针,那么_str就是随机值,交换后tmp对象的成员内容也为随机值,而随机值的空间是不能被释放的,会导致不可预知的错误,但是空指针是可以释放的,因此_str必须在初始化列表赋值成空指针。

还有现代版的赋值运算符重载:

  1. //赋值运算符重载
  2. string& operator=(string s)
  3. {
  4. swap(_str, s._str);
  5. return *this;
  6. }
  1. int main()
  2. {
  3. gxx::string s3("hello world");
  4. gxx::string s4;
  5. s4 = s3;
  6. return 0;
  7. }

赋值运算符重载,把s3的成员值给了s ,那么s和s3有同样大小的空间和值,s4想要赋值成s,把s和s4进行交换,s的内容交换给了this,s的内容现在是s4原来的内容,s4原来的内容不要了,释放s即可,s的空间释放时,s作为局部对象,出了赋值运算符重载函数作用域就会调用析构函数释放s的空间,把原来s4的内容清理掉了:

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

闽ICP备14008679号