深入C++的拷贝构造和赋值函数 (深拷贝,浅拷贝)

说明

拷贝构造函数是一种特殊的构造函数。相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。

函数的名称必须和类名称一致。

它的参数是唯一的,该参数是const类型的引用变量。例如

类X的拷贝构造函数的形式为X(X& x)。

Q:为啥拷贝构造函数的参数必须是同类对象的引用,而不能是值传递?

请看下面的例子:

  1. class A{ 
  2. public
  3.   A(A copy); //(1) 
  4.   A(const& other); //(2) 
  5. //  ... 
  6. }; 
  7. A a1; 
  8. A a2 = a1; 
class A{
public:
  A(A copy); //(1)
  A(const& other); //(2)
//  ...
};
A a1;
A a2 = a1;

一:如果拷贝构造函数允许值传递,那么就会与引用传递产生二义性。程序无法判断该调用(1)还是(2)。

二:(1)形式的拷贝构造函数实际上是非法的。这个拷贝构造函数在参数传递的过程中要调用拷贝构造函数本身,值传递又得调用拷贝构造函数本身,于是乎就陷入了无限递归之中。即会一直copy下去,因此,A(A copy)语法错误,在c++中不允许。


使用

当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。

如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的浅拷贝。


拷贝构造函数 vs 拷贝赋值函数

拷贝赋值函数参数跟拷贝构造函数相同,两者的区别在于:

构造函数是对象创建并用另一个已经存在的对象来初始化它。

赋值函数只能把一个对象赋值给另一个已经存在的对象

  1. A a1(“hi”); 
  2. A a2(“hello”) 
  3. A a3(a1);  //<span style="font-family: Arial, Helvetica, sans-serif;">调用构造函数</span> 
  4. a3 = a2;  //调用赋值函数 
A a1(“hi”);
A a2(“hello”)
A a3(a1);  //<span style="font-family: Arial, Helvetica, sans-serif;">调用构造函数</span>
a3 = a2;  //调用赋值函数
区别一目了然。


浅拷贝 vs 深拷贝

深拷贝和浅拷贝可以简单理解为:

如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝。

反之,没有重新分配资源,只是对对象中的数据成员进行简单的赋值,就是浅拷贝。默认拷贝构造函数执行的也是浅拷贝。

下面举个深拷贝的例子。

  1. #include <iostream> 
  2. using namespace std; 
  3. class CA{ 
  4.  public
  5.   CA(intb,char* cstr){ 
  6.    a=b; 
  7.    str=newchar[b]; 
  8.    strcpy(str,cstr); 
  9.   } 
  10.   CA(constCA& C){ 
  11.    a=C.a; 
  12.    str=newchar[a]; //深拷贝 
  13.    if(str!=0) 
  14.     strcpy(str,C.str); 
  15.   } 
  16.   voidShow(){ 
  17.    cout<<str<<endl; 
  18.   } 
  19.   ~CA(){ 
  20.    deletestr; 
  21.   } 
  22.  private
  23.   int a; 
  24.   char *str; 
  25. }; 
  26. int main(){ 
  27.  CAA(10,"Hello!"); 
  28.  CA B=A; 
  29.  B.Show(); 
  30.  return 0; 
#include <iostream>
using namespace std;
class CA{
 public:
  CA(intb,char* cstr){
   a=b;
   str=newchar[b];
   strcpy(str,cstr);
  }
  CA(constCA& C){
   a=C.a;
   str=newchar[a]; //深拷贝
   if(str!=0)
    strcpy(str,C.str);
  }
  voidShow(){
   cout<<str<<endl;
  }
  ~CA(){
   deletestr;
  }
 private:
  int a;
  char *str;
};
int main(){
 CAA(10,"Hello!");
 CA B=A;
 B.Show();
 return 0;
}

Q:有了浅拷贝,为啥还出现了深拷贝呢?

在某些状况下,类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,错误就来了。

废话少说,挣个有用的。

  1. class Test{ 
  2. public
  3.   Test(const char *str = "");//构造函数 
  4.   Test(const Test& copy);       //拷贝构造函数 
  5.   Test& operator = (const Test& assign);       //拷贝构造函数 
  6.   ~Test();      //析构函数 
  7. private
  8.   size_t m_size; //字符串的长度 
  9.   char *m_data;  //字符串指针 
  10. } ; 
class Test{
public:
  Test(const char *str = "");//构造函数
  Test(const Test& copy);       //拷贝构造函数
  Test& operator = (const Test& assign);       //拷贝构造函数
  ~Test();      //析构函数
private:
  size_t m_size; //字符串的长度
  char *m_data;  //字符串指针
} ;
以类Test的两个对象a、b为例,假设a.m_data的内容为“hi”,a.m_data的内容为“hello”。
i、将a赋值给b,即b.m_data = a.m_data。这就会造成错误,如下图所示:


1、b.m_data 原有内存没有释放,造成内存泄露。

2、b.m_data 和 a.m_data指向了同一块内存,任何一方变动都会影响另一方。

3、对象析构时,m_data析构了两次。

ii、拷贝构造也是同样的道理

两个成员变量指针也指向同一块内存。当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。


下面给出一种比较靠谱的实现:

  1. Test(const char *str = ""){ 
  2.   if(!str){ 
  3.     m_data = new char[1]; 
  4.     *m_data = '\0'
  5.     m_size = 0; 
  6.   }else
  7.     int len = strlen(str); 
  8.     m_data = new char[len+1]; 
  9.     strcpy(m_data,str); 
  10.     m_size = len; 
  11.   } 
  12. Test(const Test& copy){ 
  13.   int len = strlen(copy.m_data); 
  14.   m_data = new char[len+1];//深拷贝 
  15.   strcpy(m_data,copy.m_data); 
  16.   m_size = len; 
  17. Test& operator = (const Test& assign){ 
  18.   if(this != assign){ 
  19.     int len = strlen(assign.m_data); 
  20.     char *temp = new char[len+1];//深拷贝 
  21.     strcpy(temp,assign.m_data); 
  22.     delete [] m_data; 
  23.     m_data = temp; 
  24.     m_size = len; 
  25.     temp = NULL; 
  26.   } 
  27.   return *this
  28. ~Test(){ 
  29.   delete [] m_data; 
  Test(const char *str = ""){
    if(!str){
      m_data = new char[1];
      *m_data = '\0';
      m_size = 0;
    }else{
      int len = strlen(str);
      m_data = new char[len+1];
      strcpy(m_data,str);
      m_size = len;
    }
  }
  Test(const Test& copy){
    int len = strlen(copy.m_data);
    m_data = new char[len+1];//深拷贝
    strcpy(m_data,copy.m_data);
    m_size = len;
  }
  Test& operator = (const Test& assign){
    if(this != assign){
      int len = strlen(assign.m_data);
      char *temp = new char[len+1];//深拷贝
      strcpy(temp,assign.m_data);
      delete [] m_data;
      m_data = temp;
      m_size = len;
      temp = NULL;
    }
    return *this;
  }
  ~Test(){
    delete [] m_data;
  }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值