先介绍一下浅拷贝与深拷贝的定义,再结合代码进行分析。
浅拷贝:
浅拷贝是指将一个对象的值复制到另一个对象,包括指针成员的值也进行简单的复制。这意味着两个对象会共享相同的内存地址,当其中一个对象修改了指针指向的内存时,另一个对象的指针也会受到影响。
深拷贝:
深拷贝是指在拷贝对象时,不仅复制对象本身的值,而且还会为指针成员单独分配内存空间,并将原对象指针指向的内容复制到新分配的内存中。这样,即使原对象和拷贝对象有各自独立的内存空间,彼此之间互不影响。
在C++中,对于普通变量的赋值操作,无论是原始数据类型(如int、float等)还是自定义的类对象,都是进行值的复制,这意味着在赋值操作后,原始变量和目标变量是相互独立的,它们存储的是两份完全独立的数值。
因此,普通变量的赋值操作不存在浅拷贝和深拷贝的区别。浅拷贝和深拷贝更多地涉及到涉及到指针成员的对象的拷贝行为,而对于普通变量,只需要关注其值的复制即可。
class Person
{
public:
Person() // 构造函数
{
cout << "Person的默认构造函数" << endl;
}
Person(int age, int height) // 带参构造函数
{
m_Age = age;
m_Height = new int(height); // 在堆区开辟新内存,返回地址赋值给int型指针
cout << "Person的有参构造函数" << endl;
}
~Person() // 析构函数
{
// 注意下面的判断语句暂时被注释了
/*
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
*/
cout << "Person的析构函数" << endl;
}
int m_Age;
int* m_Height;
};
void test()
{
Person p1(18,165);
cout << "p1的年龄为:" << p1.m_Age << " p1的身高为:" << *p1.m_Height << endl;
// 当调用 Person p2(p1) 时,编译器会自动生成一个拷贝构造函数,将 p1 中的成员变量 m_Age 的值复制给 p2 中的对应成员变量。
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << " p1的身高为:" << *p2.m_Height << endl;
}
int main()
{
test();
return 0;
}
这时候输出如下
可以看到p1和p2的年龄身高都是一样的,这是因为调用 Person p2(p1)
时,编译器会自动生成一个拷贝构造函数,将 p1
中的成员变量 m_Age
的值复制给 p2
中的对应成员变量,这就是浅拷贝。
同时,在 test()
函数结束后,会先调用 p2
的析构函数,然后调用 p1
的析构函数,按照对象的创建顺序逆序销毁。
那么这样子有什么问题呢,为什么要出现深拷贝?
现在把代码中注释的部分添加进去,再运行一下,结果如下所示
有些电脑的编译器可能会直接报错,也有可能出现我这样的情况,少了一行析构函数的输出以及无法用任意键关闭窗口。
这是因为没有显式定义拷贝构造函数,编译器会提供一个默认的浅拷贝构造函数。在浅拷贝构造函数中,指针m_Height会被简单地复制到新对象中,这意味着p2和p1会共享相同的内存地址。当其中一个对象被销毁时,其指针成员所指向的内存被释放,导致另一个对象的指针成员成为悬空指针。
总的来说浅拷贝带来的问题时堆区内存的重复释放。
现在用内存逻辑图解释一下
也就是说同一个内存由于浅拷贝的机制导致被析构函数释放了两遍,这明显是不行的。
那么怎么解决呢?
知道了问题所在,我们就可以自己实现拷贝构造函数实现深拷贝
此时p1与p2的构造函数是不同的,最后就不会出现交叉重复释放堆内存的情况。