C++面向对象-6-深拷贝和浅拷贝

前面一直在学习构造函数,其中拷贝构造我们带出了值传递,拷贝一个对象等概念。这篇来学习一下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++面向对象-6-深拷贝和浅拷贝_第1张图片

本示例代码并没有写出拷贝构造,但是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;

}

运行结果

C++面向对象-6-深拷贝和浅拷贝_第2张图片

代码运行起来了,好像没问题。但是这里使用了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;

}

运行的结果会报错,

C++面向对象-6-深拷贝和浅拷贝_第3张图片

看起来,我们的代码没有问题,逻辑上说得通,为什么会报错。原来就是执行了这个拷贝构造函数里面发生了问题。

C++面向对象-6-深拷贝和浅拷贝_第4张图片

分析过程:

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++面向对象-6-深拷贝和浅拷贝_第5张图片

学完本篇,要知道,什么是浅拷贝,什么是深拷贝,为什么要使用析构函数。浅拷贝就是C++编译器采用等号赋值操作,深拷贝就是重新在堆区申请开辟一个空间,在拷贝构造函数里面。

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

 

你可能感兴趣的:(C++学习笔记,浅拷贝,深拷贝,析构函数)