背景:
默认下,C++至少给一个class加上
1、默认构造函数(无参),负责初始化
2、默认析构函数(无参),负责释放内存
3、默认拷贝构造,对属性值拷贝
这三个函数。
编译器自动调用的拷贝构造函数只是简单的赋值拷贝操作,其工作原理如下:
主要的问题:
在于被拷贝的对象属性里的指针与原来的对象里的指针都指向了一个内存空间,造成在调用析构函数的时候同一堆区被delete了两次(重复释放)的非法操作。
解决方案:
改变拷贝构造函数的复制方式,让复制指针的方式不再是单纯的拷贝,而是让赋值后的指针指向不同,但内容相同。
代码实现:
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age,int hight)
{
m_age = age;
m_hight = new int(hight); // 用new在堆区创建了变量,返回值是地址,用指针接收
cout << "Person的有参构造函数调用" << endl;
}
/* 自己实现拷贝构造函数实现深拷贝 */
Person(const Person &p)
{
cout << "Person调用拷贝构造函数" << endl;
m_age = p.m_age;
m_hight = new int(*p.m_hight); // 深拷贝操作,把对象p的m_hight所指的值在堆区再new一个新的出来
}
~Person()
{
/* 如果m_hight所指的不为空,则要释放掉内存,并让m_hight重新指向空 */
if (m_hight!=NULL)
{
delete m_hight;
m_hight = NULL;
}
cout << "Person的析构函数调用" << endl;
}
int m_age = 0;
int* m_hight;
};
void test01()
{
Person p1(18,179);
cout << "p1的年龄有多大:" << p1.m_age << " 身高为:" << *p1.m_hight << endl;
Person p2(p1); // 利用自定义的拷贝构造函数
cout << "p2的年龄有多大:" << p2.m_age << " 身高为:" << *p2.m_hight << endl;
}
/* 在堆区开辟的内存,程序员要手动释放掉,并且在对象销毁前调用析构函数把堆区开辟的数据释放掉 */
/* 而编译器调用的拷贝构造函数是浅拷贝,结果就是导致堆区的内存会重复释放带来非法操作 */
int main()
{
test01();
return 0;
}
深拷贝原理所示:
何时使用深拷贝?
当类里有成员属性有在堆区开辟的(new),一定要自己提供拷贝构造函数,防止浅拷贝带来重复释放内存的问题。
内存分区
上文中提到了堆区,所以再梳理下C++内存分区模型。
总的来分可分为4种
1、代码区:主要存放函数体的二进制代码,这一部分由操作系统管理。
2、全局区:存放全局变量、静态常量(static)以及常量(字符串常量、全局常量const)。
3、栈区:由编译器自动分配释放,存放函数参数值(局部变量)、局部常量(const)等。
4、堆区:由程序员自动分配内存、释放,如果内存没有被释放,程序结束时会由OS主动回收。
代码区和全局区在程序运行前就已经存在,栈区和堆区在程序运行后才被分配。