你真的了解C++拷贝构造吗???

目录

1 . 拷贝构造(浅拷贝)

1.1形式

1.2何时调用

1.3有何功能

1.4示例问题及解决方案

2 . 深拷贝


1 . 拷贝构造(浅拷贝)

1.1形式

复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。

复制构造函数的参数可以是 const 引用,也可以是非 const 引用。 一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用,也是可以的。

如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数。大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。编译器自动生成的复制构造函数称为“默认复制构造函数”。

class CStu
{
 public:
         CStu (const CStu&) //以常引用类的参数 
        {
            
        }
}

1.2何时调用

一、新建一个对象,并将其初始化为同类现有对象时如:

class CStu
{
public:
	
};

CStu stu1; //申请一个对象

CStu stu2(stu1);
CStu stu3=stu1;   
CStu* stu4=new CStu(stu1);
//以上语句都是新建一个对象,并将新建对象初始化为stu1的内容,都将调用默认拷贝构造函数

注意赋值不会调用拷贝构造如:

CStu s1,s2;   //新建对象s1,s2
s1=s2;       
//s1为以建好的对象,这只是将s2赋值给s1,不会调用拷贝构造  这与CStu s1=s2; 不同

二、当程序生成对象副本时(弊端)

1. 函数传递对象值 

如果函数 F 的参数是类 A 的对象,那么当 F 被调用时,类 A 的复制构造函数将被调用。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。

#include<iostream>
using namespace std;
class A
{
public:
    A(A & a) //拷贝构造函数
    {
        cout<<"Copy constructor called"<<endl;
    }
};
void Func(A x) //函数形参为类
{ 
    ;
}
int main()
{
    A a;
    Func(a); //传递对象 a
    return 0;
}

 运行结果 输出了"Copy constructor called",说明调用了拷贝构造函数。这是因为 Func 函数的形参 x 在初始化时调用了复制构造函数。

前面说过,函数的形参的值等于函数调用时对应的实参,现在可以知道这不一定是正确的。如果形参是一个对象,那么形参的值是否等于实参,取决于该对象所属的类的复制构造函数是如何实现的。例如上面的例子,Func 函数的形参 a 的值在进入函数时是随机的,未必等于实参,因为复制构造函数没有做复制的工作。

以对象作为函数的形参,在函数被调用时,生成的形参要用复制构造函数初始化,这会带来时间上的开销。如果用对象的引用而不是对象作为形参,就没有这个问题了。但是以引用作为形参有一定的风险,因为这种情况下如果形参的值发生改变,实参的值也会跟着改变。

2. 函数返回对象

 如果函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用。换言之,作为函数返回值的对象是用复制构造函数初始化 的,而调用复制构造函数时的实参,就是 return 语句所返回的对象。例如下:

#include<iostream>
using namespace std;
class A
{
public:
	int v;
	A(int n)
	{
		v = n;
	}
	A(const A & a)
	{
		v = a.v;
		cout << "Copy constructor called" << endl;
	}
};
A Func()
{
	A a(4);
	return a; //返回该对象a
}
int main()
{
	cout << Func().v << endl; // Func().v等价于a.v
	return 0;
}

运行结果如下

 程序的输出结果是:
Copy constructor called
4
调用 Func 函数,其返回值是一个对象,该对象就是用复制构造函数初始化的, 而且调用复制构造函数时,实参就是 return 语句所返回的 a。复制构造函数确实完成了复制的工作,所以 Func 函数的返回值和 a 相等。

需要说明的是,有些编译器出于程序执行效率的考虑,编译的时候进行了优化,函数返回值对象就不用复制构造函数初始化了,这并不符合 C++ 的标准。上面的程序,用 Visual Studio 2013 编译后的输出结果如上所述,但是在 Dev C++ 4.9 中不会调用复制构造函数。把a 变成全局变量,才会调用复制构造函数。对这一点,读者不必深究。

1.3有何功能

 下面示例拷贝构造函数作用,新建c2时调用了拷贝构造将c2初始化为c1

#include<iostream >
using namespace std;
class Complex
{
public:
	double real, imag;
	Complex(double r, double i) 
	{
		real = r; imag = i;
	}
};
int main(){
	Complex cl(1, 2);
	Complex c2(cl);   //用复制构造函数初始化c2
	cout << c2.real << "," << c2.imag << endl;;  //输出 1,2 和对象c1中的值一模一样
	return 0;
}

1.4示例问题及解决方案

当我们类中有动态开辟内存时,新建的对象调用了拷贝构造,最后在释放内存时会导致多次释放同一块内存,导致程序崩溃。

#include<iostream>
using namespace std;
class CStu
{
public:
	int* a;
	CStu()
	{
		a = new int[1]; //动态开辟个数为1的int型数组
		a[0] = 10; 	
	}
	CStu(const CStu&b)
	{
		this->a = b.a;   //将s1中指针a的值赋值给s2对象中指针a值
						//浅拷贝,在释放内存时会造成 二次释放,导致程序崩溃
	}
	~CStu() //析构函数释放内存,每个对象作用域结束时自动调用析构函数
	{
		delete[]a;
	}
};
int main()
{	
	CStu s1;
	CStu s2 = s1;
	cout << s1.a[0] << endl;
	cout << s2.a[0] << endl;
	return 0;
}

 

解决以上问题我们要尽量避免调用拷贝构造

具体方法有:使用深拷贝、传引用 。当我们使用引用时,就可以避免生成对象副本而引起调用拷贝构造

1.如下func函数的形参使用对象的引用便不会调用拷贝构造函数

#include<iostream>
using namespace std;
class A
{
public:
    A(A & a) //拷贝构造函数
    {
        cout<<"Copy constructor called"<<endl;
    }
};
void Func(A& x) //函数形参为对象的引用  不会生成对象副本
{ 
    ;
}
int main()
{
    A a;
    Func(a); //传递对象 a
    return 0;
}

没有输出"Copy constructor called"说明没有调用拷贝构造

 2.函数返回值时加引用

#include<iostream>
using namespace std;
class A
{
public:
	int v;
	A(int n)
	{
		v = n;
	}
	A(const A & a)
	{
		v = a.v;
		cout << "Copy constructor called" << endl;
	}
};
A& Func()
{
	A a(4);
	return a; //返回该对象a的引用
}
int main()
{
	Func();
	return 0;
}

没有输出"Copy constructor called"  不会调用拷贝构造函数

 

2 . 深拷贝

如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。

#include<iostream>
using namespace std;

class CStu
{
public:
	int* a;
	CStu()
	{
		a = new int[1];
		a[0] = 10;
	}
	CStu(const CStu&b)
	{
		//this->a = b.a;   //浅拷贝,在释放内存时会造成 二次释放,导致程序崩溃

		//下面实现深拷贝
		this->a = new int[1];//重新动态开辟一个数组空间,避免两次都释放同一块空间
		memcpy(this->a, b.a, sizeof(int) * 1);//使用memcpy函数将s1.a中的值拷贝到s2.a中

	}
	~CStu()
	{
		delete[]a;
	}
};

int main()
{
	
	CStu s1;
	cout << s1.a[0] << endl;
	CStu s2 = s1;
	cout << s2.a[0] << endl;
	return 0;
}

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值