一文搞清面试高频问题:深拷贝和浅拷贝(C++)

Posted 每天告诉自己要努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞清面试高频问题:深拷贝和浅拷贝(C++)相关的知识,希望对你有一定的参考价值。

浅拷贝: 简单的赋值拷贝,默认的拷贝函数就是浅拷贝,可以理解成C++的引用,别名。新对象是旧对象的引用。
深拷贝: 在堆区重新申请内存空间,再进行拷贝操作。这样就是高级的复制,新对象跟旧对象表面看起来值都一样,但是新对象跟旧对象没有其他任何关系,每个元素对应的地址是不同的。

举个例子:
浅拷贝就是,两个一模一样的彭于晏,一个叫彭于晏1号,另外一个叫彭于晏2号,这两个人完全一模一样复制出来的,只是叫法不同。
深拷贝就是,猪八戒整容变成了彭于晏,包括身高、五官、所有的外形、声音、气质都跟整得跟彭于晏的一样,但是里面流着的还是猪血,本质上还是一头猪,所以本质上还是一个猪八戒跟一个彭于晏,只是看起来是两个彭于晏。

先说明实际应用场景:浅拷贝带来的问题就是堆区的内存重复释放;而深拷贝就是来解决这个问题的。

要了解深浅拷贝,首先要回顾一下几个知识点。一个类的对象,初始化的时候是在栈区的一块内存里,在对象调用结束后,栈区的内存会被释放。这个跟函数调用是同理的,当函数调用结束之后,也会自动释放掉刚刚函数执行使用了的内存。这种一般的声明变量,都是储存在栈区的,所以才能够在调用结束的时候被释放掉。但是,C++里的new关键字,是在堆区开辟一块新的内存,举个例子:在局部函数中new了一块内存,当局部函数调用结束之后,这块内存仍然存在,也就是这个new出来的变量在函数调用结束之后还可以被用来进行后面的操作。

另外要注意:new出来的是堆区的一段空间的首地址,因此需要用指针去接收它。

构建一个人类,以年龄age为例
class Person {
public:
	Person(int age) {
		m_age = new int(age);
	}
	
	Person(const Person& p) {
		//m_age = p.m_age;  编译器默认的方式是浅拷贝
		m_age = new int(*p.m_age); //自己构造的深拷贝
	}
	
	~Person() {
	 	if (m_age) delete m_age;
	 } 

private:
	int* m_age;
};

再来梳理一下逻辑:
首先,因为这个类的初始化构造里面存在new,需要向堆区请求开辟一块内存,堆区内存手动开辟,同时也需要手动释放。
因此,需要在析构函数里面写一个delete来释放掉这个内存。此时如果不用到“拷贝构造”这个操作,是不会出现问题的;
但是,如果用到了拷贝构造,就会出现重复释放内存的问题。
这是因为默认的构造函数是浅拷贝,也就是简单的赋值操作,以上述的“人类”为例子,默认的浅拷贝的赋值操作是 m_age = p.m_age,这是一种简单无脑的“复制”,堆区的内存只有一块,但是被这样无脑的“复制”导致有两个指针指向了同一块内存,因此浅拷贝会导致同一块内存被释放两次。很明显,解决方案就是,使堆区的内存变成两块,并且两个指针分别指向这两块不同的内存,而且两块内存里面的值是相同的,所以自己手写一个深拷贝构造函数来代替默认的构造函数,手写的深拷贝构造函数里面再开辟一块新的内存来“复制”年龄的值,这样子的操作就是深拷贝了,深拷贝解决了浅拷贝带来的堆区内存重复释放的问题。

看到这里,会不会有这样的疑问:new了内存之后如果不delete那就没事了?
回答:new了内存之后,如果不delete,那么这块内存将会被一直占用,白白浪费了一块内容。这样会导致很多问题,比如在内存占用得很多的时候电脑会死机。

以上是关于一文搞清面试高频问题:深拷贝和浅拷贝(C++)的主要内容,如果未能解决你的问题,请参考以下文章

C++入门深拷贝和浅拷贝详解

C++入门深拷贝和浅拷贝详解

**Python中的深拷贝和浅拷贝详解

C++深拷贝和浅拷贝细节理解

Java面试题|深拷贝和浅拷贝区别是什么?

C++ 类的深拷贝和浅拷贝完美解决