【C++】拷贝构造函数 Copy Constructor (深拷贝、浅拷贝、赋值操作符)

一、拷贝构造函数的性质

定义:

  拷贝构造函数是一个成员函数,它用同一个类的一个对象初始化另一个对象。

语法:

classname (const classname &obj) {
   // body of constructor
}

 note: 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值赋值。

拷贝构造函数的作用:

1.从同一类型的另一个对象初始化一个对象。
2.将对象拷贝作为一个函数的参数。
3.拷贝一个对象以返回它的函数。

拷贝构造函数的意义:

浅拷贝 拷贝后对象的物理状态相同
深拷贝 拷贝后对象的逻辑状态相同

note:编译器提供的拷贝构造函数只进行浅拷贝。

什么时候调用拷贝构造函数:

1 .当类对象返回一个值;
2.当类的对象被作为参数通过值传递(到函数)时;
3.当一个类的对象是基于同一个类的另一个对象构造时;
4.当编译器生成一个临时对象时

Example1:

#include <iostream>

using namespace std;

class Line {

public:
	int getLength(void);
	Line(int len);             // 普通构造函数
	Line(const Line &obj);  // 拷贝构造函数
	~Line();                     // 析构函数

private:
	int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len) {
	cout << "普通构造函数分配指针ptr" << endl;

	// 给指针ptr分配内存;
	ptr = new int;
	*ptr = len;
}

Line::Line(const Line &obj) { //obj是用于初始化另一个对象的对象的引用。
	cout << "拷贝构造函数分配指针 ptr." << endl;
	ptr = new int;
	*ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void) {
	cout << "释放内存!" << endl;
	delete ptr;
}

int Line::getLength(void) {
	return *ptr;
}

void display(Line obj) {
	cout << "line 的长度 : " << obj.getLength() << endl;
}

// Main 函数实现
int main() {
	Line line(10); //调用普通构造函数 Line(int len);

	display(line); //调用拷贝构造函数 Line(const Line &obj);
	getchar();
	return 0;
}

 编译运行,结果如下:

调用拷贝构造函数的另一种方式 Line line2 = line1; 如下:

int main() {

	Line line1(10);

	Line line2 = line1; // 这也是调用拷贝构造函数

	display(line1);
	display(line2);

	getchar();
	return 0;
}

 二、深拷贝与浅拷贝

Example2:

#include <stdio.h>

class Test
{
private:
	int i;
	int j;
	int* p;  
public:
	int getI()
	{
		return i;
	}
	int getJ()
	{
		return j;
	}
	int* getP()
	{
		return p;
	}
	Test(const Test& t) //拷贝构造函数
	{
		i = t.i;
		j = t.j;
		p = new int;

		*p = *t.p;
	}
	Test(int v)
	{
		i = 1;
		j = 2;
		p = new int;

		*p = v;
	}
	void free()
	{
		delete p;
	}
};

int main()
{
	Test t1(3);  //调用构造函数Test(int v)
	Test t2(t1);  //调用拷贝构造函数Test(const Test& t)

	printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), t1.getP()); //*t1.p = 4887464
	printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), t2.getP()); //*t2.p = 4887512
		
	t1.free();
	t2.free();
	getchar();

	return 0;
}

编译运行, 

可见,t1 和 t2所指的地址空间是不同的 ,因为t2进行深拷贝。

我们不妨把注释掉下列代码:

	Test(const Test& t) //拷贝构造函数
	{
		i = t.i;
		j = t.j;
		p = new int;

		*p = *t.p;
	}

然后编译运行:

可见,指针 t1 和 t2的地址相同,说明了t1 和t2 都指向了同一片内存空间。

 问题分析:

①浅拷贝:默认构造函数只做浅拷贝。

t1 和t2 都指向了同一片内存空间:

                                                       

 当释放内存地址时,t1 和 t2释放的是同一块内存地址,此时会发生编译错误:

 ②深拷贝:只能与用户定义的复制构造函数。在用户定义的复制构造函数中,我们确保复制对象指向新的内存位置的指针(或引用)。

                                                        

可见,Bag2 深拷贝 Bag1,Bag2指向了一片新的内存地址。

Question: 什么时候需要进行深拷贝?

当对象中有成员指代了系统中的资源:

1.成员指向了动态内存空间
2.成员打开了外存中的文件
3.成员使用了系统中的网络端口

一般性原则:

  自定义拷贝构造函数,必然需要实现深拷贝!

 

三、拷贝构造函数 VS 赋值操作符

下面的两个语句哪个调用拷贝构造函数,哪个调用赋值操作符?


MyClass t1, t2; 
MyClass t3 = t1;  // ----> (1) 
t2 = t1;          // -----> (2)  

答:

当一个已经初始化的对象从另一个现有对象分配一个新值时,调用赋值操作符:

t2 = t1;  //调用赋值操作符, 相当于 "t2.operator=(t1);"

当新对象从现有对象创建时,就会调用拷贝构造函数,作为现有对象的拷贝:

Test t3 = t1;  // 调用拷贝构造函数, 相当于"Test t3(t1);"

 Test:


#include<iostream> 
#include<stdio.h> 

using namespace std;

class Test
{
public:
	Test() {}
	Test(const Test &t)
	{
		cout << "调用拷贝构造函数 " << endl;
	}
	Test& operator = (const Test &t)
	{
		cout << "调用赋值操作符 " << endl;
	}
};

int main()
{
	Test t1, t2;
	t2 = t1;       //调用赋值操作符
	Test t3 = t1;  //调用赋值操作符
	getchar();
	return 0;
}

编译运行,结果如下:

编译运行,结果如下:

 

<本文完> 

Reference:

1)www.tutorialspoint.com

2)www.geeksforgeeks.org

3)唐佐林《C++深度解析教程》 

猜你喜欢

转载自blog.csdn.net/qq_40416052/article/details/82734346
今日推荐