需要的预备知识:
构造函数(无参或默认构造函数、有参构造函数、拷贝构造函数),析构函数,new和delete操作符(堆区内存的开辟与释放)。
浅拷贝
浅拷贝是简单的赋值操作,只拷贝一个指针,没有开辟新的地址,拷贝的指针和原来的指针指向同一块地址,这样调用析构函数释放堆区资源的时候,因为如果原来的指针指向的内存被释放了,后面再释放浅拷贝的指针,会带来内存重复释放的问题,从而报错。
使用编译器默认提供的拷贝构造函数,就是浅拷贝
如下代码来自黑马程序员C++课程,我们new一个指针m_Height来测试下:
#include<iostream>
using namespace std;
//浅拷贝
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age, int height)
{
m_Age = age;
m_Height = new int(height);//开辟堆区内存,放置height
cout << "Person的有参构造函数调用" << endl;
}
~Person()//析构函数
{
//析构代码,将堆区开辟的数据做释放操作
if (m_Height != nullptr)
{
delete m_Height;
m_Height = nullptr;//防止野指针出现
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;//年龄
int *m_Height;//身高,想把身高创建在堆区,new一个,所以定义一个指针
};
void test01()
{
Person p1(18,160);//有参构造函数调用
//析构函数里不写delete m_Height,即不释放申请的堆内存,就能正常输出,且对于身高指针p1.m_Height而言,会发现p1.m_Height和p2.m_Height两者一样,同样的地址
cout << "p1的年龄为" << p1.m_Age << "p1的身高为"<<*p1.m_Height<<endl;
Person p2(p1);//没自己写拷贝构造函数,用编译器自己提供的,进行了简单的赋值操作(浅拷贝)
cout << "p2的年龄为" << p2.m_Age << "p2的身高为" <<*p2.m_Height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
不释放m_Height指向的内存,并且输出指针而不解引用时,即代码如下
结果如下
可以看到两个地址是一模一样的,这就是浅拷贝。但是如果执行完整的代码,即在构造函数里执行delete m_Height。会出现以下结果:
这就是重复释放了内存。因为test01()里有两个局部对象p1和p2,在函数调用完会自动执行构造函数,构造函数里我们写了delete m_Height,即释放指针指向的内存,他会根据先进后出规则,先释放p2的m_Height指向的内存,再释放m_Height指向的内存,而这两个内存是同一块,所以出现重复释放问题。
深拷贝
浅拷贝的问题我们用深拷贝解决!我们不使用编译器提供的拷贝构造函数,而是自己写一个
Person(const Person &p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//编译器默认的拷贝构造其实是m_height = p.m_Height,即直接把p1的m_Height等号赋值给p2的m_Height
//深拷贝操作
m_Height = new int(*p.m_Height);//自己实现的话,重新new一个,开辟新的空间
}
和编译器提供的拷贝构造函数的操作,区别就在于m_Height = new int(*p.m_Height),我们自己重新申请了一个内存空间
完整代码如下
#include<iostream>
using namespace std;
//深拷贝
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age, int height)
{
m_Age = age;
m_Height = new int(height);//开辟堆区内存,放置height
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//编译器默认的拷贝构造其实是m_height = p.m_Height,即直接把p1的m_Height等号赋值给p2的m_Height
//深拷贝操作
m_Height = new int(*p.m_Height);//自己实现的话,重新new一个,开辟新的空间
}
~Person()//析构函数
{
//析构代码,将堆区开辟的数据做释放操作
if (m_Height != nullptr)
{
delete m_Height;
m_Height = nullptr;//防止野指针出现
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;//年龄
int *m_Height;//身高,想把身高创建在堆区,new一个,所以定义一个指针
};
void test01()
{
Person p1(18,160);//有参构造函数调用
//输出身高指针p1.m_Height,会发现p1.m_Height和p2.m_Height两者一样,同样的地址
cout << "p1的年龄为" << p1.m_Age << "p1的身高为"<<*p1.m_Height<<endl;
Person p2(p1);//没自己写拷贝构造函数,用编译器自己提供的,进行了简单的赋值操作(浅拷贝)
cout << "p2的年龄为" << p2.m_Age << "p2的身高为" <<*p2.m_Height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
运行结果
我们去掉解引用*,输出地址看看
所以,即使p1和p2两个对象的身高指针都叫m_Height,但是在我们自己实现的拷贝构造函数里,重新给height分配了内存空间,所以两者的地址是不一样的,这样就避免了浅拷贝在调用析构函数时带来的内存重复释放问题。