持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情
1、问题背景
当我们创建一个类是,编译器会为我们自动编写默认构造函数、默认析构函数和默认的拷贝函数。默认的拷贝函数,我们称之为浅拷贝。
2、浅拷贝
我们创建一个Person类,设置一个整型变量age和一个指针变量 hight(在堆区开辟,需要程序员自己去释放内存)。当我们把p1拷贝给p2时,编译器会逐字节地将p1内存中的数据复制到p2中。这就导致p1和p2 的higth指针都指向了堆区的同一块内容。在程序结束前,编译器会自动调用Person的析构函数,去释放内存,析构函数与构造函数的调用顺序是相反的,所以p2的内存会先被释放,此时堆区的0x0011被释放一次,等到p1被析构时,会再一次释放0x0011,这是一个非法操作,所以编译器抛出异常。为了解决这个问题,就不能再使用编译器默认的拷贝函数,需要程序员自己重写拷贝函数——深拷贝。
3、深拷贝
深拷贝时,我们会在堆区新开辟一个空间去储存p2的指针变量hight,就不会造成某一块内存别重复释放。
class Person
{
public:
int age;
int *higth;//开辟到堆区
Person(int age,int h);
~Person();
Person(const Person &p);
};
Person::Person(const Person &p)//自己写一个拷贝构造函数来解决浅拷贝的问题
{
cout << "深拷贝构造函数调用" << endl;
age = p.age;
//higth=p.higth//浅拷贝默认实现拷贝函数的方式
higth = new int (*p.higth);//在堆区开去一个新内存
}
Person::Person(int a,int h)
{
higth = new int(h);//什么是new??
age = a;
cout << "Person含参构造函数的调用!" << endl;
}
Person::~Person()
{
if (higth != NULL)//因为对取数据需要程序员自己进行释放
{
delete higth;//释放堆区数据
higth = NULL;
}
cout <<"Person的析构函数调用!"<<endl;
}
void test01()
{
Person p1(19, 170);
cout << "p1的年龄;" << p1.age << " p1的身高" << *p1.higth << endl;
Person p2(p1);
cout << "p2的年龄;" << p2.age << " p2的身高" << *p2.higth << endl;
//如使用浅拷贝,先释放p2,p1也就没有了,再次释放就是非法操作。
}
如果成员变量有在堆区开辟的,那么一定要自己书写拷贝构造函数,否则会出现浅拷贝问题。
4、赋值运算重载解决浅拷贝问题
拷贝构造函数是将p1的东西赋给p2,所以我们可以赋值运算符进行重载解决浅拷贝问题(在我的上一篇文章介绍了运算符重载可以类比这看一看,谢谢!!!)。
//赋值运算符重载
#include <iostream>
using namespace std;
class Person
{
public:
Person(){}
Person(int a)
{
age = new int(a);
}
~Person()
{
if (age != NULL)
{
delete age;
age = NULL;
}
}
Person& operator=(Person &p)
{
if (age != NULL)
{
delete age;
age = NULL;
}
age = new int(*p.age);//这一部分跟深拷贝函数写法一样。
return *this;//实现链式赋值
}
int *age;
};
void test01()
{
Person p1(18);
Person p2;
p2 = p1;
Person p3;
p3 = p2 = p1;//链式赋值
cout << "p1:" << *p1.age << endl;
cout << "p2:" << *p2.age << endl;
cout << "p3:" << *p3.age << endl;
}
int main()
{
test01();
}