本文主要参考:
http://blog.csdn.net/lwbeyond/article/details/6202256
拷贝函数的定义
拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
对于一个类X, 如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
(PS:volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问)
拷贝构造函数举例:
#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;
}
拷贝构造函数的调用时机
- 对象以值传递的方式传入函数参数
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)
时,主要发生了三件事:
1.test赋值给形参时,会产生一个临时变量tmp;
2.然后会调用拷贝构造函数,将test的值赋值给tmp;
3.等g_Fun(test)
执行完,就会将tmp析构。
- 对象以值传递的方式从函数返回
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时,主要发生如下几件事:
1.首先会产生一个局部变量xxx;
2.然后会将tmp的值赋值给xxx,此时有点类似CExample xxx(tmp)代码片
;
3.紧接着就会析构掉tmp变量;
4.等g_Fun()
执行完毕,就会析构掉xxx变量。
- 对象需要通过另外一个对象进行初始化;
CExample A(100);
CExample B = A;
// CExample B(A);
浅拷贝与深拷贝
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间。
example1:
class Rect
{
public:
Rect() // 构造函数,计数器加1
{
count++;
}
~Rect() // 析构函数,计数器减1
{
count--;
}
static int getCount() // 返回计数器的值
{
return count;
}
private:
int width;
int height;
static int count; // 一静态成员做为计数器
};
int Rect::count = 0; // 初始化计数器
int main()
{
Rect rect1;
cout<<"The count of Rect: "<<Rect::getCount()<<endl;
Rect rect2(rect1); // 使用rect1复制rect2,此时应该有两个对象
cout<<"The count of Rect: "<<Rect::getCount()<<endl;
return 0;
}
由于Rect rect2(rect1);
调用的是拷贝构造函数,所以静态计数器变量count并没有实行+1操作,程序结束后,存在两个对象,将实行两次析构函数的调用,所以最后静态计数器变量count将变成负数。
example2:
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;
}
这里编译会报错。主要是因为在运行定义rect1对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:
在使用rect1复制rect2时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,如下图所示:
当程序执行完毕,将对同一内存地址进行两次释放,这就是编译报错的原因。
解决上面的问题,就是编写拷贝构造函数,在里面动态赋值:
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; // 一指针成员
};