不好驯服的析构函数(转)

不好驯服的析构函数

本文假定您对C++的面向对象机制有一些了解,并且对指针比较清楚。您可以随便转载,但是必须保证文章的完整性,并且注明出处。

很愿意和您交朋友:xiaobo68688@qq.com

注:想问我是学C还是C++的朋友就不要发邮件了,谢谢!

首先看一个深拷贝的例子

 

 

 

  1. #include <iostream>   
  2. using namespace std;   
  3. const int SIZE          = 9;   
  4. const int DEFAULT_NUM   = 6;   
  5. class A   
  6. {   
  7. private:   
  8.     int *p;   
  9.     int size;   
  10. public:   
  11.     A(A&);   
  12.     A();   
  13.     ~A();   
  14.     void    set();      //设置A的各个元素的值   
  15.     void    showEle();  //输出p的各个元素   
  16.     void    showAdd();  //输出p的地址   
  17.     int     *getP();   
  18.     int     getSize();   
  19. };   
  20.   
  21. A::A()   
  22. {   
  23.     p       = NULL;   
  24.     size    = 0;   
  25. }   
  26.   
  27. A::~A()   
  28. {   
  29.     delete []p;   
  30.     p = NULL;   
  31.     size = 0;   
  32. }   
  33.   
  34. A::A(A &right)   
  35. {   
  36.     size = right.getSize();   
  37.     p = new int[size];   
  38.   
  39.     memcpy(p,right.getP(), sizeof(int)*size);   
  40. }   
  41.   
  42. void A::set()//设置A的各个元素的值   
  43. {   
  44.     p       = new int[SIZE];   
  45.     size    = SIZE;   
  46.   
  47.     for(int n=0; n<size; n++)   
  48.     {   
  49.         p[n] = DEFAULT_NUM;   
  50.     }   
  51. }   
  52.   
  53. void A::showEle()   
  54. {   
  55.     for(int n=0; n<size; n++)   
  56.     {   
  57.         cout<<p[n]<<endl;   
  58.     }   
  59. }   
  60.   
  61. void A::showAdd()   
  62. {   
  63.     cout<<p<<endl;   
  64. }   
  65.   
  66. int *A::getP()   
  67. {   
  68.     return p;   
  69. }   
  70.   
  71. int A::getSize()   
  72. {   
  73.     return size;   
  74. }   
  75.   
  76. int main()   
  77. {   
  78.     A a;   
  79.     a.set();   
  80.     a.showEle();   
  81.     a.showAdd();   
  82.   
  83.     A b(a);   
  84.     b.showEle();   
  85.     b.showAdd();   
  86.   
  87.     return 0;   
  88. }  

可能我写的有点乱,成员变量包括一个指针,其实是一个数组,想要深拷贝。

 

所以我们需要重载拷贝构造函数。

初学C++的时候,直接记住了参数是引用类型,当时对语言没有深入的理解(虽然现在也不深入),也就没有往心里去,这些天模拟STL,遇到了好多关于深拷贝的问题,而且这问题出的很怪异(我会在下午提出),因此想到了拷贝构造函数,所以在这里需要深究一下。

拷贝构造函数的参数是引用类型。

我们可以试想一下不是引用的情况,也就是说,拷贝构造函数是下边这个样子:

 

 

  1. A::A(A right)   
  2. {   
  3.     size = right.getSize();   
  4.     p = new int[size];   

  1.   
  2.     memcpy(p,right.getP(), sizeof(int)*size);   
  3. }  

参数传递的是一个对象,按值传递,那么这个对象就会被复制到right,复制的时候又会调用拷贝构造函数,那又会复制一个对象到第二次调用的这个拷贝构造函数里边,因此又需要第三次调用拷贝构造函数。。。想一下,是不是无限循环?

 

简单地说:传值需要调用拷贝构造函数,而不是引用类型的拷贝构造函数又需要调用拷贝构造函数。因此无限循环。

 

 

Ok,把引用的这个问题说明白,下边说一下我这几天遇到的这个问题:

请先看一段代码:

 

  1. #include <iostream>   
  2. #include <vector>   
  3. using namespace std;   
  4.   
  5. class A   
  6. {   
  7. private:   
  8.     int *p;   
  9.     int size;   
  10. public:   
  11.     void    setSize(int);   
  12.     void    setP(int*);   
  13.     int     getSize()   const;   
  14.     int     *getP()     const;   
  15.     void    fun();              //设置相应的值,为拷贝做准备   
  16.     void    show();   
  17.             ~A();               //析构函数   
  18.             A();   
  19.        
  20.     A operator = (A right)  //重载的等号   
  21.     {   
  22.         int *rightP = right.getP();   
  23.   
  24.         size    = right.getSize();   
  25.         p       = new int[size];   
  26.         memcpy(p, rightP, size*sizeof(int));   
  27.   
  28.         return *this;   
  29.     }   
  30.        
  31. ;   
  32. };   
  33. void A::setSize(int newSize)   
  34. {   
  35.     size = newSize;   
  36. }   
  37.   
  38. void A::setP(int *newP)   
  39. {   
  40.     p = newP;   
  41. }   
  42.   
  43. void A::show()   
  44. {   
  45.     for(int n=0;n<size;n++)   
  46.     {   
  47.         cout<<p[n]<<endl;   
  48.     }   
  49. }   
  50.   
  51. int *A::getP() const  
  52. {   
  53.     return p;   
  54. }   
  55.   
  56. int A::getSize() const  
  57. {   
  58.     return size;   
  59. }   
  60.   
  61. A::A()   
  62. {   
  63.     p = NULL;   
  64.     size = 0;   
  65. }   
  66.   
  67. void A::fun()   
  68. {   
  69.     p = new int [9];   
  70.     for(int n=0;n<9;n++)   
  71.     {   
  72.         p[n] = n+1;   
  73.     }   
  74.     size = 9;   
  75. }   
  76.   
  77. //关键是下边的析构函数,如果进行delete操作,该程序就会出错   
  78. A::~A()   
  79. {   
  80.     delete []p;   
  81.     p = NULL;   
  82.     size = 0;   
  83. }   
  84.   
  85. int main()   
  86. {   
  87.     A a;   
  88.     a.fun();   
  89.     cout<<"a.show():"<<endl;   
  90.     a.show();   
  91.   
  92.     A b;   
  93.     b = a;   
  94.     cout<<"b.show()"<<endl;   
  95.     b.show();   
  96.   
  97.     cout<<"a.show()"<<endl;   
  98.     a.show();   
  99.   
  100.     return 0;   
  101. }  

可以试着运行一下,在我电脑上边出现了下边这个错误

 

 

很恐怖的错误。呵呵,让我们详细分析一下。

第一次a.show()成功,说明对象a建立没有问题;b.show()失败,说明没有复制成功;第二次a.show()失败,说明a被修改了,可以猜想一下,是不是被调用了析构函数?

现在重载 的时候是按值传递,那么,我们传进去的是地址psize

之后是执行拷贝的函数,这个没啥问题,把right中的相关数据拷贝。

然后就开始执行析构函数了,首先析构的是传进来的参数,也就是right,由于rightp和主函数中ap相等,因此rightpdelete了也就是apdelete了,在这里,我们把主函数中的a不小心析构了一点点(把pdelete了)。因为函数的返回的是值,所以第二步析构的是(*this),这样,就把复制后的pdelete了。到这里,我们悲剧地把内存中所有的pdelete了。

所以,当再次输出ab的时候,全都是随机值。

 

细心的朋友可能会发现,当第二次输出ab的时候仍然是输出了10个,也就是开始的size值。asize10不难理解。由于析构函数是在函数返回后调用的,所以返回的bsize是不改变的(返回是在改变以前)。

 

我们试着改一下:

下边是我在参数上边加了&,这样,当再次输出a的时候没有错误

 

 

  1. operator = (A &right) //重载的等号   
  2. {   
  3.     int *rightP = right.getP();   

  1.     size = right.getSize();   
  2.     p = new int[size];   
  3.     memcpy(p, rightP, size*sizeof(int));   
  4.     return *this;   
  5. }  

下边是在参数right和返回值都加了&这样就没有错误了

 

 

 

  1. A &operator = (A &right)    //重载的等号   
  2. {   
  3.     int *rightP = right.getP();   

  1.     size = right.getSize();   
  2.     p = new int[size];   
  3.     memcpy(p, rightP, size*sizeof(int));   
  4.     return *this;   
  5. }  

正好符合我们的分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值