C++深拷贝和浅拷贝

来源:互联网 发布:二本秋招 知乎 编辑:程序博客网 时间:2024/06/08 10:59

简单的说,就是在oop中会涉及到对象的成员中会有指针
如果在复制对象时,只是简单的值复制,则两个对象共用一段内存区域
这是比较危险的。如果一个对象析构函数回收了这段内存,另外一个对象就坐等悲剧了
所以需要深复制!!就是把内存区域也复制一份副本
这样说比较简单啦

对于普通类型的对象来说,它们之间的复制是很简单的,例如:
int a=88;
int b=a;
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。下面看一个类对象拷贝的简单例子。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
usingnamespace std;
  
classCExample {
private:
     inta;
public:
     CExample(intb)
     { a=b;}
     voidShow ()
     {
        cout<<a<<endl;
    }
};
  
int main()
{
     CExample A(100);
     CExample B=A;
     B.Show ();
     return0;
}

 

运行程序,屏幕输出100。从以上代码的运行结果可以看出,系统为对象B分配了内存并完成了与对象A的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。下面举例说明拷贝构造函数的工作过程。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
usingnamespace std;
  
classCExample {
private:
    inta;
public:
    CExample(intb)
    { a=b;}
      
    CExample(constCExample& C)
    {
        a=C.a;
    }
    voidShow ()
    {
        cout<<a<<endl;
    }
};
  
int main()
{
    CExample A(100);
    CExample B=A;
    B.Show ();
    return0;
}

 

当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用以下情况都会调用拷贝构造函数:
(1)一个对象以值传递的方式传入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要通过另外一个对象进行初始化

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

自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

浅拷贝和深拷贝

  在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。下面举个深拷贝的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
usingnamespace std;
classCA
{
 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:
  inta;
  char*str;
};
  
int main()
{
 CA A(10,"Hello!");
 CA B=A;
 B.Show();
 return0;
}

 

来总结一下关于 深拷贝与浅拷贝需要知道的基本概念和知识:

(1)什么时候用到拷贝函数?

  a.一个对象以值传递的方式传入函数体; 
  b.一个对象以值传递的方式从函数返回;
  c.一个对象需要通过另外一个对象进行初始化。

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

 

(2)是否应该自定义拷贝函数?

 

 自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

(3)什么叫深拷贝?什么是浅拷贝?两者异同?

 

 

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

 

(4)深拷贝好还是浅拷贝好?

如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

c++默认的拷贝构造函数是浅拷贝

浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:
class A { public: A(int _data) : data(_data){} A(){}
private: int data; };
int main() { A a(5), b = a; // 仅仅是数据成员之间的赋值 }
这一句b = a;就是浅拷贝,执行完这句后b.data = 5;
如果对象中没有其他的资源(如:堆,文件,系统资源等),则深拷贝和浅拷贝没有什么区别,但当对象中有这些资源时,例子:
class A { public: A(int _size) : size(_size){data = new int[size];} // 假如其中有一段动态分配的内存 A(){}; ~A(){delete [] data;} // 析构时释放资源
private: int* data; int size; }
int main() { A a(5), b = a; // 注意这一句 }
这里的b = a会造成未定义行为,因为类A中的复制构造函数是编译器生成的,所以b = a执行的是一个浅拷贝过程。我说过浅拷贝是对象数据之间的简单赋值,比如:
b.size = a.size; b.data = a.data; // Oops!
这里b的指针data和a的指针指向了堆上的同一块内存,a和b析构时,b先把其data指向的动态分配的内存释放了一次,而后a析构时又将这块已经被释放过的内存再释放一次。
对同一块动态内存执行2次以上释放的结果是未定义的,所以这将导致内存泄露或程序崩溃。
所以这里就需要深拷贝来解决这个问题,深拷贝指的就是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象的另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。如:
class A { public: A(int _size) : size(_size){data = new int[size];} // 假如其中有一段动态分配的内存 A(){}; A(const A& _A) : size(_A.size){data = new int[size];} // 深拷贝 ~A(){delete [] data;} // 析构时释放资源
private: int* data; int size; }
int main() { A a(5), b = a; // 这次就没问题了 }
总结:
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。

原创粉丝点击