今天和朋友讨论一个新手写的小程序,由于那个程序的问题太多了,并且自己的基础也不是很扎实,所以就变成了我两对问题的讨论,中间讨论的一个问题就是复制构造函数,讨论了半天,终于是扯清了,于是分享一下。
首先说一下复制构造函数究竟是个什么东西,本质上说,复制构造函数也就是一种构造函数,只是他的使用情况不同,不需要程序员对其进行显式调用。
在说一下复制构造函数的使用时间,主要由三种情况会使用:
1.使用同一类型的类对象对另一个类对象进行初始化时;
2.当一个对象以值的形式传递给一个函数时;
3.当一个函数按值返回一个对象时。
在一般情况下,我们并不知道复制构造函数的存在,因为当我们没有对复制构造进行申明时,系统会自动为我们分配一个默认复制构造函数,该函数的功能仅仅是对类中的成员进行简单的复制到另一个对象,如果在类中不使用指针变量时,这个函数不会出现什么问题,但当类中存在指针时,问题就出现了。
当我们使用一个类的对象a对另一个类对象b进行初始化时,系统调用默认复制构造函数对其进行初始化,如是,系统就会将b中成员的指针赋值给a中指针成员,是a、b中的指针成员指向同一片内存,如是,当我们在某一时刻结束a或b的生命期(将其销毁delete)时,b或a中的指针指向了一个已经析构了的堆空间,当我们在使用这个指针时,由于他已经不存在了,系统就会崩溃。
上面所说的就是“浅复制”,可以看出,系统提供的复制构造函数在此时已经不能满足我们的要求了,我们需要自己设计自己的复制构造函数,使上面a、b对象成员指向的内存空间分离,这样我们在其中的一个对象生命期结束时,仍能保证另一个对象可以正常使用。
如何去设计这个复制构造函数呢?思路很简单,就是当我们进入复制构造函数进行成员变量复制时,我们不使用复制地址的方式,我们为新的成员变量开辟一块新的内存空间,然后将原成员变量的值写到这块内存空间中,这样就完成了分离工作,这也就是我们所谈到的“深层复制”。下面是一个例子:
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深度复制 新开辟一块堆控件来进行复制
if(str!=0)
strcpy(str,C.str); //仅仅对成员变量的值进行复制,保证复制之后两个对象之间指向不同堆空间
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
也许,有比较细心的朋友会看到,我在构造函数CA(int b,char* cstr)中开辟了一块str的堆空间,又在复制构造函数CA(const CA& C)中开辟了一块str的堆空间,但只析构一次,会不会造成内存泄露?嘿嘿,担心多余了,我刚开始就说过了,复制构造函数也是构造函数,因此他们中间只有一个会执行,不会同时开辟两块空间。
下面说一下复制构造函数使用的时间,前面已经说了有三种情况,现在就不再说了,主要是对其进行一下实例分析吧。
#include "iostream"
using namespace std;
class CA
{
public:
CA(){ cout<< "construct be called." << endl;}
CA(CA& ra){ cout << "copy construct be called. "<< endl;}
};
CA fun(CA a)
{
CA na(a);
return na;
}
int main(int argc, char* argv[])
{
CA fa;
CA fb = fun(fa);
return 0;
}
先说一下结果吧,复制构造函数会执行四次!到底是那四次呢?
1、调用fun(fa)时,即情况2,我们知道fun函数申明为CA fun(CA a),对象按值传递,那么系统会调用复制构造函数创建一个临时对象a
2、调用CA na(a)时,即情况1
3、调用return na时,即情况3
4、调用CA fb = fun(fa)是,即情况1,使用fun函数的返回值对fb进行初始化
好了,水平有限,就写这样了,欢迎拍砖
注:文中代码均摘自网络,因为看到这些例子都很好的,不需要自己再编写了,呵呵