摘要:
1.代码格式牢记。类名(const 类名 &X)
2.该函数的三种调用时机。
3.浅拷贝与深拷贝(是否有动态成员变量)。
一、什么是拷贝构造函数
例1:
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
//构造函数
CExample(int b)
{ a = b;}
//拷贝构造函数
CExample(const CExample& C)
{
a = C.a;
}
//一般函数
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B = A; // CExample B(A); 也是一样的
B.Show ();
return 0;
}
系统为对象B分配了内存并完成了与对象A的复制过程。就类对象而言,
相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。CExample(const CExample& C)就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,它
必须的一个参数是本类型的一个引用变量。
二、拷贝构造函数的调用时机
在C++中,下面三种对象需要调用拷贝构造函数!
1.对象以值传递的方式传入函数参数
例2:
#include <iostream>
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a = b;
cout<<"creat: "<<a<<endl;
}
//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}
//析构函数
~CExample()
{
cout<< "delete: "<<a<<endl;
}
void Show ()
{
cout<<a<<endl;
}
};
//全局函数,传入的是对象
void g_Fun(CExample C)
{
cout<<"test"<<endl;
}
int main()
{
CExample test(1);
//传入对象
g_Fun(test);
return 0;
}
g_Fun(test)
执行步骤:
①test对象传入形参时,会产生一个临时变量,设为C。
②然后调用拷贝构造函数把test的值给C。
③等g_Fun()执行完成后,析构C对象。
2.对象以值传递的方式从函数返回
例3:
#include <iostream>
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a = b;
}
//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}
void Show ()
{
cout<<a<<endl;
}
};
//全局函数
CExample g_Fun()
{
CExample temp(0);
return temp;
}
int main()
{
g_Fun();
return 0;
}
当g_Fun()函数执行到return时,会产生以下几个重要步骤:
①先会产生一个临时变量,就叫XXXX吧。
②然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);
③在函数执行到最后先析构temp局部变量。(析构顺序)
④等g_Fun()执行完后再析构掉XXXX对象。
3.对象需要另外一个对象初始化
CExample A(100);
CExample B = A;
最后一句会调用拷贝构造函数。
若程序如下,则不会调用拷贝构造函数。即:CExample B = A;与B = A;的区别。
CExample a(1);
CExample b(2);
b = a;
最后一句为调用拷贝构造函数,调用了等号运算符重载。程序代码示例如下(注意其中this与*this区别)
CExample &operator=( const CExample &rhs )
{
if ( this == &rhs ) {
return *this;
}
this->a = rhs.a;
return *this;
}
三、浅拷贝与深拷贝
系统自带默认的拷贝构造函数。
1.浅拷贝
只对对象中的数据成员进行简单的赋值。大多数情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么拷贝就会出问题了。如下代码:
class Rect
{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};
int main()
{
Rect rect1;
Rect rect2(rect1); // 复制对象
return 0;
}
error:进行对象复制时,对于动态分配的内容没有进行正确的操作。
在运行定义rect1对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:
在使用rect1复制rect2时,由于执行的是拷贝,只是将成员的值进行赋值,这时rect1.p=rect2.p,也即这两个指针指向了堆里的同一个空间,如下如所示:
当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值。解决办法就是使用“深拷贝”。
2.深拷贝
在“深拷贝”的情况下,对于对象中动态成员,应该重新动态分配空间,如下所示。
class Rect
{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
Rect(const Rect& r)
{
width = r.width;
height = r.height;
p = new int; // 为新对象重新动态分配空间
*p = *(r.p);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};
完成对象复制后,内存情况如下所示:
此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。
参考: