C++类的拷贝(复制)构造函数深入理解

  • Post author:
  • Post category:其他



目录


一、拷贝构造函数的基本了解和使用


二、拷贝构造函数的一些注意事项


三、 拷贝构造函数的调用时机


①使用一个对象 “直接构造(显式构造 )” 或 “初始化(隐式构造 )”另一个同类对象


关于拷贝构造函数与赋值函数的区别与联系


②作为函数的形式参数时


③作为函数的返回值


四、深拷贝与浅拷贝的区别与联系


五、总结:拷贝构造函数的难点主要是:


一、拷贝构造函数的基本了解和使用


①拷贝构造函数又被称为复制构造函数,是一种特殊的构造函数,它被/编辑器/在一个对象初始化另一个对象时调用。


②拷贝构造函数的形参列表上需要加上const来防止被引用的对象的值发生改变,不加也可以,但是不加const,会使代码显得不规范。


代码示例:

class Test{
 public:
 /*const 为了防止引用对象被修改
 也可以不写const,而是直接写 Test(Test &obj){}
 但是写const更加规范一些*/
 Test(const Test&obj)//拷贝构造函数无返回值,参数为本类对象的引用。
 {
     //函数体里面一般只写赋值语句。
 }   
}

二、拷贝构造函数的一些注意事项


①拷贝构造可以形成重载,但必须第一个形参为当前类的对象的引用(不建议对其

函数进行加参数重载)。


②如果类设计者没有显式写出拷贝构造函数,编辑器会给出一个默认拷贝构造函数,但是这个默认拷贝构造函数只能够进行浅拷贝的操作,如果类设计者需要进行深拷贝的操作,那么需要自行设计。

三、 拷贝构造函数的调用时机



①使用一个对象 “直接构造(显式构造 )” 或 “初始化(隐式构造 )”


另一个同类对象


#include<iostream>

using namespace std;

class test
{
public:
    test() {
        cout << "调用默认构造函数" << " "<<this<<endl;/*利用this指针更清晰的观察编辑器创建对象和析构的具体过程*/
    }
    test(const test& obj)//写const标准一些
    {
        cout << "调用了拷贝构造函数" << " "<<this<<endl;/*一般里面只写赋值语句,但是初学者可以在拷贝构造函数体里面写这条语句以便观察什么时候调用了拷贝构造函数。*/
    }
    ~test() {
        cout << "调用了析构函数" << " "<<this<<endl;
    }
};
int main()
{
    //直接定义obj_1对象,会调用构造函数test()
    test obj_1;
    //用obj_1对象显式构造(直接构造)obj_2对象,会调用拷贝构造函数test(const test& obj)
    test obj_2(obj_1);
    //用obj_1对象隐式构造(初始化)obj_3对象,会调用拷贝构造函数test(const test& obj)
    test obj_3 = obj_1;
    //这里涉及到拷贝构造函数与赋值函数的知识点 
    return 0;
}


关于拷贝构造函数与赋值函数的区别与联系



只要记住拷贝构造函数是在对象构建的时候调用,而赋值函数是在对象已经构建结束的时候调用。


运行结果参考:



②作为函数的形式参数时


#include<iostream>
#include<cstdlib>
using namespace std;

class Test {
public:
	Test() {
		cout << "调用默认构造函数" <<" "<< this << endl;/*利用this指针来将编辑器对于对象构造与析构的隐式过程显示化*/
	}
	Test(const Test& obj)
	{
		cout << "调用复制构造函数" << " "<<this << endl;

	}

	~Test() { cout << "调用析构函数" <<" "<<this << endl; }
};
void fun(Test oj)//作为函数形式参数
{
	cout << "形参是类对象" << endl;
}
int main()
{
	Test obj_1;
	system("pause");//用于观察什么时候调用这些类成员函数
	fun(obj_1);//构造临时对象
	system("pause");
	return 0;
}
 


运行结果参考:



③作为函数的返回值


#include<iostream>
#include<cstdlib>
using namespace std;

class Test {
public:
	Test() {
		cout << "调用了默认构造函数" << " "<<this<<endl;
		system("pause");
	}
	Test(Test& obj)
	{
		cout << "调用了复制构造函数" << " "<<this<<endl;
		system("pause");

	}
	~Test() {
		cout << "调用了析构函数" << " " << this << endl;
		system("pause");

	}
};
Test fun()//对象作为返回值时调用拷贝构造函数
{
	Test obj;
	system("pause");
	return obj;
}
int main()
{
	Test obj_1;
	obj_1 = fun();//这里涉及到一个函数返回值调用拷贝构造函数创建临时对象的次数的问题
	return 0;
}


运行结果参考:

调用拷贝构造函数创建临时对象的次数的问题参考:@

http://t.csdn.cn/TzQpg

四、深拷贝与浅拷贝的区别与联系


以题目来引入深拷贝:

创建一个类,数据属性中包含一个在堆中new创建的含5个大小的整型数组,然后在主函数中创建一个对象obj_1给其数组赋值,再在主函数中创建一个对象obj_2,通过对象赋值操作初始化obj_2(提醒要用拷贝构造!!!),最后用obj_2将数组里的值逆向输出 并且最后记得释放堆中内存(很重要,不然会导致内存泄漏,系统是不会帮你释放堆中内存的,提醒用析构函数来实现!!!)

运行例子:

输入:1 2 3 4 5

输出:5 4 3 2 1


一般情况下:(浅拷贝)

#include<iostream>
using namespace std;
class test
{
public:
 int *p;
public:
 test()
 {
 p = new int[5];
 cout << "调用构造函数test()" << endl;
 }
 test(const test& obj)
 {
 this->p = obj.p;
 cout << "调用拷贝构造函数test(const test& obj)" << endl;
 }
 ~test()
 {
 if (p != nullptr)
 {
 delete p;
 p = nullptr;
 }
 cout << "调用析构函数test()" << endl;
 }
};
int main()
{
 //创建对象obj_1
 test obj_1;
 for (int i = 0; i < 5; i++)//给对象obj_1输入数组值
 {
 cin >> obj_1.p[i];
 }
 //创建对象obj_2,并用obj_1对象初始化(隐式构造)obj_2,将obj_1堆中数组初始化obj_2堆中数
组(相当于将数组值赋给obj_2数组)
 test obj_2 = obj_1;
 for (int i = 4; i >= 0; i--)//通过对象obj_2,逆向输出数组值
 {
 cout << obj_2.p[i] << " ";
 }
 cout << endl;
 return 0;
}
 
 


运行结果:(错误)



分析:为什么会出现这种问题?



会报错的根本原因在于拷贝构造函数为浅拷贝的直接赋值

test(const test& obj)
{
this->p = obj.p;
cout << "调用拷贝构造函数test(const test& obj)" << endl;
}



this->p = obj.p 这个赋值操作,直接导致堆中内存的复用,释放时出现释放同一内存的危险操作;


正确的解决方案

test(const test& obj)
 {
 this->p = new int[5];//先自己申请独立内存空间,就可以防止同一内存复用
        for(int i=0;i<5;i++)/*再将对象obj.p里的数组值,逐一赋给需被拷贝对象自己申请的独立
内存空间,完成拷贝构造操作*/
       {
          this->p[i]= obj.p[i];
       } 
 cout << "调用拷贝构造函数test(const test& obj)" << endl;
 }
 


运行结果参考:



完整代码:

#include<iostream>
using namespace std;
class test
{
public:
	int* p;
public:
	test()
	{
		p = new int[5];
		cout << "调用构造函数test()" << endl;
	}
	test(const test& obj)
	{
		this->p = new int[5]; 
		for (int i = 0; i < 5; i++) 
		{
		   this->p[i] = obj.p[i];
		}
		cout << "调用拷贝构造函数test(const test& obj)" << endl;
	}
	 

	~test()
	{
		if (p != nullptr)
		{
			delete p;
			p = nullptr;
		}
		cout << "调用析构函数test()" << endl;
	}
};
int main()
{
	//创建对象obj_1
	test obj_1;
	for (int i = 0; i < 5; i++)//给对象obj_1输入数组值
	{
		cin >> obj_1.p[i];
	}
	/*创建对象obj_2, 并用obj_1对象初始化(隐式构造)obj_2,将obj_1堆中数组初始化obj_2堆中数
	组(相当于将数组值赋给obj_2数组)*/
		test obj_2 = obj_1;
	for (int i = 4; i >= 0; i--)//通过对象obj_2,逆向输出数组值
	{
		cout << obj_2.p[i] << " ";
	}
	cout << endl;
	return 0;
}



五、总结:拷贝构造函数的难点主要是:




①拷贝构造函数作为返回值和函数形参时,它被编辑器调用的次数。



②拷贝构造函数的深拷贝和浅拷贝的问题。



版权声明:本文为m0_66444993原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。