浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
先看一个类的声明:运行会报错。
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age, int height)
{
mAge = age;
mHeight = new int(height); // 堆区的数据由程序员手动开辟,也需要由程序员手动释放。
cout << "有参构造函数被调用。" << endl;
}
~Person()
{
// 析构函数的用途:将堆区开辟的数据释放。
if (mHeight != NULL)
{
delete mHeight;
// 为了防止野指针的出现:
mHeight = NULL;
}
cout << "析构函数被调用。" << endl;
}
int mAge;
int* mHeight; // 开辟数据要放在堆区。
};
void test01()
{
Person p1(18, 170);
cout << "p1的年龄为:" << p1.mAge << "身高为:" << *p1.mHeight << endl;
Person p2(p1); // 通过编译器提供的拷贝构造函数,会做浅拷贝。
cout << "p2的年龄为:" << p2.mAge << "身高为:" << *p2.mHeight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
堆区的数据由程序员开辟,也要由程序员手动释放。
- 何时释放?对象销毁之前释放。比如说test01函数执行完了,就需要销毁p1和p2了。
- 如何销毁?对象销毁时,需要调用析构函数,在析构函数中,增加对内存的释放。
- 析构函数的用途:将堆区开辟的数据释放。
出错原因:
第33行代码:在上一节中,我们提到:通过编译器提供的拷贝构造函数,会做浅拷贝。
对象p1有两个属性:
- 第一个属性是mAge,在栈中开辟空间,复制时,直接复制到另一个栈空间中。
- 第二个属性是mHeight,指针。在堆中开辟空间,栈中存储的是:在堆中的地址,在拷贝构造时,只是浅拷贝把栈中存的地址给复制传过去了。所以p1和p2的第二个指针类型的属性都指向同一块堆区。
此时,如果我们调用析构函数。析构函数会释放堆中数据mHeight。test01函数中的这个两个局部变量(栈中)都要释放。
栈的规则:先进后出。
因此会先释放p2。p2释放完成后,释放p1。问题出现了。
我们已经在销毁p2时,将堆中数据释放了。销毁了之后,p1还要去销毁释放这块堆区内存,非法操作。堆区内存重复释放。
浅拷贝带来的问题就是:堆区内存重复释放。非法操作。
解决办法:浅拷贝的问题要利用深拷贝进行解决。
前面采用编译器自带的拷贝构造函数,所以可以自己写一个拷贝构造函数。然后在堆中重新申请一块内存,然后也存放170数据。
mHeight = new int(*p.mHeight);
解引用,将值提取,然后new在堆中开辟空间,存入堆区空间,然后赋值。
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age, int height)
{
mAge = age;
mHeight = new int(height); // 堆区的数据由程序员手动开辟,也需要由程序员手动释放。
cout << "有参构造函数被调用。" << endl;
}
// 自己实现一个拷贝构造函数 解决浅拷贝带来的问题。
Person(const Person& p)
{
cout << "Person拷贝构造函数的调用。" << endl;
mAge = p.mAge;
//mHeight = p.mHeight; // 编译器默认实现的就是这行代码。 会报错。
// 深拷贝操作:
mHeight = new int(*p.mHeight); // 在堆中重新开辟一块空间。来存储这个值。完成赋值。
}
~Person()
{
// 析构函数的用途:将堆区开辟的数据释放。
if (mHeight != NULL)
{
delete mHeight;
// 为了防止野指针的出现:
mHeight = NULL;
}
cout << "析构函数被调用。" << endl;
}
int mAge;
int* mHeight; // 开辟数据要放在堆区。
};
void test01()
{
Person p1(18, 170);
cout << "p1的年龄为:" << p1.mAge << "身高为:" << *p1.mHeight << endl;
Person p2(p1); // 通过编译器提供的拷贝构造函数,会做浅拷贝。
cout << "p2的年龄为:" << p2.mAge << "身高为:" << *p2.mHeight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
如果有属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。