前面一直在学习构造函数,其中拷贝构造我们带出了值传递,拷贝一个对象等概念。这篇来学习一下C++的一个非常容易面试遇到的问题,也就是学习过程中的一个坑。什么是浅拷贝和深拷贝,两者的区别是什么。
浅拷贝:简单的赋值拷贝操作
前面我们学习的拷贝构造函数就是浅拷贝
根据前面的代码,我们把Point换成Person类
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "调用了Person类默认构造函数" << endl;
}
Person(int age)
{
cout << "调用了Person类有参构造函数" << endl;
m_Age = age;
}
public:
int m_Age;
};
void test01()
{
Person p;
p.m_Age = 18;
Person p1(p);
cout << "p1的年龄是:" << p1.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果
本示例代码并没有写出拷贝构造,但是C++编译器自动调用了拷贝构造,所以P1的年龄也变成了18岁,这个就是浅拷贝的过程。
深拷贝:在堆区重新申请空间,进行拷贝操作
先修改下面代码,就是添加一个身高的属性,是一个int类型指针,然后在有参构造里面是有new关键字在堆区开辟一个空间来新建一个身高,用指针去接收。
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "调用了Person类默认构造函数" << endl;
}
Person(int age, int hight)
{
cout << "调用了Person类有参构造函数" << endl;
m_Age = age;
// 使用new 关键字在内存堆区创建一个对象
m_Hight = new int(hight); // 用指针去接收new的内存地址
}
public:
int m_Age;
int *m_Hight; //身高的指针
};
void test01()
{
Person p(18, 180);
p.m_Age = 18;
cout << "p的年龄是:" << p.m_Age << ",p的身高为:" << *p.m_Hight << endl; //解引用输出指针里面内容
Person p1(p);
cout << "p1的年龄是:" << p1.m_Age << ",p1的身高为:" << *p1.m_Hight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果
代码运行起来了,好像没问题。但是这里使用了new这个关键字来在堆区申请空间,这个申请是程序员自己控制,那么就需要负责回收内存,这个怎么回收,接下来,我们的析构函数就派上用场了。就是在main函数中调用test01, test01执行完里面的对象都会销毁,在销毁之前,我们调用析构函数来回收内存。
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "调用了Person类默认构造函数" << endl;
}
Person(int age, int hight)
{
cout << "调用了Person类有参构造函数" << endl;
m_Age = age;
// 使用new 关键字在内存堆区创建一个对象
m_Hight = new int(hight); // 用指针去接收new的内存地址
}
~Person()
{
if (m_Hight != NULL)
{
delete m_Hight; // 使用关键字delete来回收内存
m_Hight = NULL; //设置为null, 防止野指针
}
cout << "调用了Person类析构函数" << endl;
}
public:
int m_Age;
int *m_Hight; //身高的指针
};
void test01()
{
Person p(18, 180);
p.m_Age = 18;
cout << "p的年龄是:" << p.m_Age << ",p的身高为:" << *p.m_Hight << endl; //解引用输出指针里面内容
Person p1(p);
cout << "p1的年龄是:" << p1.m_Age << ",p1的身高为:" << *p1.m_Hight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行的结果会报错,
看起来,我们的代码没有问题,逻辑上说得通,为什么会报错。原来就是执行了这个拷贝构造函数里面发生了问题。
分析过程:
1)开始执行test01()方法,这个方法入栈
2)执行Person p, 走的有参构造
3)执行拷贝构造 Person p1,这个时候p1会把p对象中身高那个指针也会拷贝过去,使用一样的内存地址。
3)由于栈的特点,先进后出,所以这里,test01()执行完是先p1对象执行销毁操作
4)p1对象调用析构函数,走if判断,判断身高这个指针不为空,所以进行删除和回收操作。
5)p对象也要执行析构函数,也对身高指针进行判断,由于前面是拷贝一模一样指针,所以之前堆中内存已经被删除,这次再次判断,已经是非法操作,所以包异常断点信息。
通过这个例子,我们看到了浅拷贝的一个弊端:堆区的内存重复释放。
为了解决这个浅拷贝的问题,C++中使用深拷贝,就是在拷贝到堆区的指针和之前的指针地址不一样,但是指针内数据是一样的。
深拷贝:在堆区重新申请空间,进行拷贝操作
深拷贝的技术就是解决重复在堆区释放内存的操作,就是保住每次释放的内存不一样。我们在实现拷贝构造函数里面去实现深拷贝。
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "调用了Person类默认构造函数" << endl;
}
Person(int age, int hight)
{
cout << "调用了Person类有参构造函数" << endl;
m_Age = age;
// 使用new 关键字在内存堆区创建一个对象
m_Hight = new int(hight); // 用指针去接收new的内存地址
}
Person(const Person &p)
{
cout << "调用了Person类拷贝构造函数" << endl;
m_Age = p.m_Age;
//m_Hight = p.m_Hight; //编译器默认拷贝构造就是这行代码
//深拷贝操作
m_Hight = new int(*p.m_Hight);
}
~Person()
{
if (m_Hight != NULL)
{
delete m_Hight; // 使用关键字delete来回收内存
m_Hight = NULL; //设置为null, 防止野指针
}
cout << "调用了Person类析构函数" << endl;
}
public:
int m_Age;
int *m_Hight; //身高的指针
};
void test01()
{
Person p(18, 180);
p.m_Age = 18;
cout << "p的年龄是:" << p.m_Age << ",p的身高为:" << *p.m_Hight << endl; //解引用输出指针里面内容
Person p1(p);
cout << "p1的年龄是:" << p1.m_Age << ",p1的身高为:" << *p1.m_Hight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
执行结果
学完本篇,要知道,什么是浅拷贝,什么是深拷贝,为什么要使用析构函数。浅拷贝就是C++编译器采用等号赋值操作,深拷贝就是重新在堆区申请开辟一个空间,在拷贝构造函数里面。
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。