cc++c++11 浅拷贝和深拷贝

cc++c++11 浅拷贝和深拷贝,第1张

1.1 拷贝构造函

c++通过拷贝构造函数实现对象拷贝.所以先介绍一下拷贝构造函数.

实例:

CA A(100,"123")//构造函数赋值

CExample B=A//拷贝构造函数赋值

CExample C(A)//拷贝构造函数赋值

CExample DD=A//赋值构造函数赋值

g_fun(A)//传值拷贝调用对象构造函数

拷贝构造函数实现分析:

调用g_Fun()时,会产生以下几个重要步骤:

(1).函数调用传值拷贝,会先会产生一个临时变量,就叫 C 吧。

(2).然后调用拷贝构造函数把A的值给C.整个这两个步骤有点像:CA C(A)

(3).等g_Fun()执行完后, 析构掉 C 对象。C对象完成了在g_Fun()函数内部的工作.

1.2 浅拷贝

浅拷贝只拷贝基本数据类型(非指针变量).

对于指针变量,对象B的指针变量会 指向 对象A的指针变量内存,不会拷贝.

类缺省拷贝构造函数是浅拷贝.上例中的拷贝构造函数的实现就是浅拷贝.

浅拷贝的问题是如果对象中变量带有指针,则会发生错误.因为两个指针指向同一个内存,一个对象修改,另一个对象的值也被更改了.

当在析构的时候,会发生两次free同一个内存,造成错误.

下面的link介绍了浅拷贝和c的简单神拷贝.c++深拷贝参见1.3节,c的高级深拷贝参见2.2节。

参见 https://blog.csdn.net/cyy_0802/article/details/80374812

1.3 c++深拷贝

在拷贝构造函数中分配内存,将入参对象的指针变量指向的内存,全部拷贝一份就是深拷贝。

实例分析

CA A(10,"Hello!")//构造函数初始化对象。

CA B=A//拷贝构造函数.

上例将str的内容拷贝一份,实现了深拷贝.

扩展问题,如果类中变量不是char* str,而是另一个对象的指针会怎么样?

2.1 浅拷贝

和上面的c++浅拷贝一样. 两个指针指向同一个内存.

2.2 深拷贝

(1) 如果struct中没有指针变量

直接拷贝内存即可.

(2) 如果struct中带有指针变量

需要自己实现拷贝函数,将每个item拷贝一份.(实现类似c++的拷贝构造函数)

扩展情况:如果将char *data换成struct S_NodeA *nodeA会怎么样呢?

CopyStruct将非struct的item拷贝一份。nodeA需要嵌套拷贝.调用拷贝S_Node的函数CopyNode完成拷贝.

总结:浅拷贝就是指针赋值,不分配内存,两个指针指向一个内存. 深拷贝就是拷贝指针指向的内存.如果有嵌套对象的话,嵌套拷贝.

c++11 使用移动构造函数实现深拷贝

移动构造函数的原理是,指针A和B同时指向一块内存,然后将原来的A指针置NULL. 这样避免了两个指针指向同一个内存,也避免了内存拷贝.

浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:

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// 这次就没问题了

}

总结:

深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。

1.什么是拷贝构造函数:

拷贝构造函数嘛,当然就是拷贝和构造了。(其实很多名字,只要静下心来想一想,就真的是顾名思义呀)拷贝又称复制,因此拷贝构造函数又称复制构造函数。百度百科上是这样说的:拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)。此函数经常用在函数调用时用户定义类型的值传递及返回。

2.拷贝构造函数的形式

Class X

{

public:

X()

X(const X&)//拷贝构造函数

}

2.1为什么拷贝构造参数是引用类型?

其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动被调用来生成函数中的对象(符合拷贝构造函数调用的情况)。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象,这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。

3.拷贝构造函数调用的三种形式

3.1.一个对象作为函数参数,以值传递的方式传入函数体;

3.2.一个对象作为函数返回值,以值传递的方式从函数返回;

3.3.一个对象用于给另外一个对象进行初始化(常称为复制初始化)。

总结:当某对象是按值传递时(无论是作为函数参数,还是作为函数返回值),编译器都会先建立一个此对象的临时拷贝,而在建立该临时拷贝时就会调用类的拷贝构造函数。

4.深拷贝和浅拷贝

如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。(位拷贝又称浅拷贝,后面将进行说明。)自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

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

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

#include <iostream>

using namespace std

class CA

{

public:

CA(int b,char* cstr)

{

  a=b

  str=new char[b]

  strcpy(str,cstr)

}

CA(const CA&C)

{

  a=C.a

  str=new char[a]//深拷贝

  if(str!=0)

strcpy(str,C.str)

}

void Show()

{

  cout<<str<<endl

}

~CA()

{

  delete str

}

 private:

int a

char *str

}

int main()

{

  CA A(10,"Hello!")

  CA B=A

  B.Show()

  return 0

}

浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。一定要注意类中是否存在指针成员。

5.拷贝构造函数与“=“赋值运算符

例如:

class CExample

{};

int main()

{

CExample e1 = new CExample

CExample e2 = e1//调用拷贝构造函数

CExample e3(e1)//调用拷贝构造函数

CExample e4

e4 = e1//调用=赋值运算符

}

通常的原则是:①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;②在提供拷贝构造函数的同时,还应该考虑重载"="赋值 *** 作符号。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12146437.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存