【C++】浅拷贝和深拷贝

  • Post author:
  • Post category:其他


深拷贝与浅拷贝

简单的来说,


【浅拷贝】


是增加了一个指针,指向原来已经存在的内存。而


【深拷贝】


是增加了一个指针,并新开辟了一块空间

让指针指向这块新开辟的空间。



【浅拷贝】


在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误





浅拷贝

为了形象化说明什么是深拷贝和浅拷贝,我们就先写一个String类

类里面包含


【构造函数】





【拷贝构造函数】





【赋值运算符重载】


,以及


【析构函数】





【输出操作符“<<”的重载】




  1. class


    String


  2. {


  3. public


    :


  4. String(

    const




    char


    *pStr =


    “”


    )


  5. {


  6. if


    (NULL == pStr)


  7. {

  8. pstr =

    new




    char


    [1];


  9. *pstr =

    ‘\0’


    ;


  10. }


  11. else




  12. {

  13. pstr =

    new




    char


    [strlen(pStr)+1];


    //加1,某位是’\0′




  14. strcpy(pstr,pStr);

    //用拷贝字符串的函数




  15. }

  16. }


  17. String(

    const


    String &s)


  18. :pstr(s.pstr)

    //浅拷贝的问题,指向同一块空间,可能造成释放的错误 ,这是浅拷贝的缺点




  19. {}


  20. String& operator=(

    const


    String&s)


  21. {


  22. if


    (


    this


    != &s)


  23. {


  24. delete


    [] pstr;


    //将原来所指向的空间释放




  25. pstr = s.pstr;

    //让pstr重新指向s的pstr所指向的空间(也会导致错误)




  26. }


  27. return


    *


    this


    ;


  28. }


  29. ~String()

  30. {


  31. if


    (NULL != pstr)


  32. {


  33. delete


    [] pstr;


    //释放指针所指向的内容




  34. pstr = NULL;

    //将指针置为空




  35. }

  36. }


  37. friend


    ostream&operator<<(ostream & _cout,


    const


    String &s)


  38. {

  39. _cout<<s.pstr;


  40. return


    _cout;


  41. }


  42. private


    :



  43. char


    *pstr;


  44. };

经过测试之后,在某种情况下是可以正常运行的,在特定情况下是不可以正常的运行的

举一个不能正常运行的例子



  1. int


    main()


  2. {

  3. String s1(

    “sss”


    );


  4. String s2(s1);

  5. String s3(NULL);

  6. s3 = s1;

  7. cout<<s1<<endl;

  8. cout<<s2<<endl;

  9. cout<<s3<<endl;


  10. return


    0;


  11. }


在该例子中,我们有三个String类的对象,s1调用



【构造函数】



存入字符”sss”

s2调用


【拷贝构造函数】


来利用s1进行初始化,s3则用


【赋值运算符】


来进行初始化

黑框框里输出了三个“sss”

然而!


这是为什么呢?

通过监视,我们发现他们指向的都是同一块空间,因为地址都是


【0x01044570】

在让我们看看


【析构函数】



  1. ~String()


  2. {


  3. if


    (pstr != NULL)


  4. {


  5. delete


    [] pstr;


  6. pstr = NULL;

  7. }

  8. }


当我们释放s3的时候,可以正常释放

然而当释放s2的时候,由于


【s3已经释放过了】


,所以s2所指向的这段空间已经不属于s1或者s2了

此时我们调用delete释放的时候,必然会崩溃


(毕竟人家本来就不属于你呀)










深拷贝





深拷贝以及深浅拷贝的对比

深拷贝和浅拷贝的不同之处,仅仅在于修改了下


【拷贝构造函数】


,以及


【赋值运算符的重载】



  1. String(


    const


    String &s)


  2. :pstr(

    new




    char


    [strlen(s.pstr)+1])


  3. {

  4. strcpy(pstr,s.pstr);

  5. }


  6. String& operator=(

    const


    String &s)


  7. {


  8. if


    (


    this


    != &s)


  9. {


  10. char


    * tmp =


    new




    char


    [strlen(s.pstr)+1];


    //动态开辟一个临时变量,然后将pstr指向这一个新的临时变量里





  11. delete


    [] pstr;


    //将原来的空间进行释放




  12. strcpy(tmp,s.pstr);

    //将s.pstr里的内容复制到临时变量中




  13. pstr = tmp;

    //pstr指向临时变量的这段空间




  14. }


  15. return


    *


    this


    ;


  16. }

对比一下浅拷贝的


【拷贝构造函数】





【赋值运算符重载】



  1. String(


    const


    String &s)


  2. :pstr(s.pstr)

    //浅拷贝的问题,指向同一块空间,可能造成释放的错误 ,这是浅拷贝的缺点




  3. {}


  4. String& operator=(

    const


    String&s)


  5. {


  6. if


    (


    this


    != &s)


  7. {


  8. delete


    [] pstr;


    //将原来所指向的空间释放




  9. pstr = s.pstr;

    //让pstr重新指向s的pstr所指向的空间(也会导致错误)




  10. }


  11. return


    *


    this


    ;


  12. }











深拷贝完整版



  1. class


    String


  2. {


  3. public


    :


  4. String(

    const




    char


    * pStr =


    “”


    )


  5. {

  6. cout<<

    “String()”


    <<endl;



  7. if


    (NULL == pStr)


  8. {

  9. pstr =

    new




    char


    [1];


  10. *pstr =

    ‘\0’


    ;


  11. }


  12. else




  13. {

  14. pstr =

    new




    char


    [strlen(pStr)+1];


  15. strcpy(pstr,pStr);

  16. }

  17. }

  18. String(

    const


    String &s)


  19. :pstr(

    new




    char


    [strlen(s.pstr)+1])


  20. {

  21. strcpy(pstr,s.pstr);

  22. }


  23. String& operator=(

    const


    String &s)


  24. {


  25. if


    (


    this


    != &s)


  26. {


  27. char


    * tmp =


    new




    char


    [strlen(s.pstr)+1];


    //pstr;





  28. delete


    [] pstr;


  29. strcpy(tmp,s.pstr);

  30. pstr = tmp;

  31. }


  32. return


    *


    this


    ;


  33. }


  34. ~String()

  35. {


  36. if


    (NULL != pstr)


  37. {


  38. delete


    [] pstr;


  39. pstr = NULL;

  40. }

  41. }


  42. private


    :



  43. char


    *pstr;


  44. };

除此之外,我们可以简化一下深拷贝的


【拷贝构造函数】





【赋值运算符重载】




  1. <span style=


    “color:#000000;”


    >String(


    const


    String& s)


  2. :_ptr(NULL)

  3. {

  4. String temp(s._ptr);

  5. std::swap(_ptr, temp._ptr);

  6. }

  7. </span>




【拷贝构造函数】


里用s定义临时变量,临时变量会自动调用构造函数开辟空间

然后用swap这个函数交换两个变量之间的内容

原来的内容在temp,并且出了


【拷贝构造函数】


就销毁了,避免了内存泄漏






  1. String& operator=(


    const


    String& s)


  2. {


  3. if


    (


    this


    != &s)


  4. {

  5. String temp(s);

  6. swap(_ptr, temp._ptr);

  7. }


  8. return


    *


    this


    ;


  9. }


  1. String& operator=(


    const


    String& s)


  2. {


  3. if


    (


    this


    != &s)


  4. {

  5. String temp(s._ptr);

  6. swap(_ptr,temp._ptr);

  7. }


  8. return


    *


    this


    ;


  9. }


  1. String& operator=(String temp)


  2. {

  3. swap(_ptr,temp._ptr);


  4. return


    *


    this


    ;


  5. }






【赋值运算符重载】



里,也可以用到类似的方法。在第三个方法里,直接传入一个临时变量,连if判断都可以省去了









总结



【浅拷贝】


只是增加了一个指针,指向已存在对象的内存。



【深拷贝】


是增加了一个指针,并

新开辟了一块空间

,让指针指向这块新开辟的空间。






【浅拷贝】


在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误