深浅拷贝是面试经典问题,也是常见的坑
参考链接:https://blog.csdn.net/caoshangpa/article/details/79226270?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.channel_param
浅拷贝:简单的复制拷贝操作
浅拷贝带来的问题就是:堆区的内存重复释放,
要利用深拷贝解决
深拷贝:在堆区重新申请空间,进行拷贝操作
**总结:**如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include "pch.h"
#include <iostream>
using namespace std;
class person {
public:
person() {
cout << "无参构造函数调用" << endl;
}
person(int age, int height) {
m_age = age;
m_Height = new int(height);//用new开辟到堆区,会返回为相应数据类型的指针类型
cout << "有参构造函数调用" << endl;
}
//自己实现一个拷贝构造函数,解决浅拷贝带来的问题
person(const person &p) {
cout << "person拷贝构造函数调用" << endl;
m_age =p.m_age;
m_Height = p.m_Height;//编译器默认实现的是这行代码
//深拷贝
m_Height = new int(*p.m_Height);//括号里面是解引用
}
~person() {
//析构代码,将堆区开辟的数据做释放操作
if (m_Height = NULL) {
delete m_Height;//释放
m_Height = NULL;//防止野指针出现
}
cout << "析构函数调用" << endl;
}
int m_age;
int* m_Height;//指向身高,开辟到堆区
};
void test01() {
person p1(18,160);
cout << "p1的年龄" <<p1.m_age<< endl;
cout << "p1的身高" << *p1.m_Height << endl;
person p2(p1);//当有自己写的深拷贝构造函数时,会调用自己写的。
cout << "p2的年龄" << p2.m_age<<endl;
}
int main()
{
test01();
return 0;
}
深拷贝和浅拷贝
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
【区别】:
- 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
【理解】:浅拷贝:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深拷贝:在计算机中开辟一块新的内存地址用于存放复制的对象。
例如:
假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
C++中拷贝构造函数的浅拷贝与深拷贝
- 定义了一个类,并且实例化了一个对象,想要复制此对象时(也就是已有的对象),如果没有自己重写拷贝构造函数,那么会默认调用系统的拷贝构造函数(浅拷贝)。
【例子1】
#include <iostream>
using namespace std;
class Student
{
private:
int num;
char *name;
public:
Student();
~Student();
};
Student::Student()
{
name = new char(20);
cout << "Student" << endl;
}
Student::~Student()
{
cout << "~Student " << (int)name << endl;
delete name;
name = NULL;
}
int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2(s1);// 复制对象
}
system("pause");
return 0;
}
运行后:
执行结果:调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,这会导致什么问题呢?name指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致崩溃!
【原因】
这是由于编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针name拷贝后会出现两个指针指向同一个内存空间。
所以,在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。
【例子2】添加了自己定义拷贝构造函数的例子:
#include <iostream>
using namespace std;
class Student
{
private:
int num;
char *name;
public:
Student();
~Student();
Student(const Student &s);//拷贝构造函数,const防止对象被改变
};
Student::Student()
{
name = new char(20);
cout << "Student" << endl;
}
Student::~Student()
{
cout << "~Student " << (int)name << endl;
delete name;
name = NULL;
}
Student::Student(const Student &s)
{
name = new char(20);
memcpy(name, s.name, strlen(s.name));
cout << "copy Student" << endl;
}
int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2(s1);// 复制对象
}
system("pause");
return 0;
}
执行结果:
- 调用一次构造函数,以及一次自定义拷贝构造函数,
- 两次析构函数。两个对象的指针成员所指内存不同。
总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
【注意】
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑以下两种情形:
1.当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
2.当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。
3.浅拷贝带来问题的本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。