什么时候会调用默认构造函数呢?
答案是对于未被初始化的对象,程序将使用默认构造函数来创建,也就是说默认构造函数将用于初始化过程中。
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型通常如下:
Class_name(const Class_name &)什么时候会调用复制构造函数呢?
1、新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
例如,假设motto是一个StringBad对象,则下面4种声明都将调用复制构造函数:
其中中间的2种声明可能会使用复制构造函数直接创建metoo和also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pstring指针。
2、每当程序生成了对象副本时,编译器都将使用复制构造函数。
具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着创建原始变量的一个副本。
编译器生成临时对象时,也将使用复制构造函数。例如,将3个Vector对象相加时,编译器可能生成临时的Vector对象来保存中间结果。何时生成临时对象随编译器而异,但无论是哪种编译器,当按值传递和返回对象时,都将调用复制构造函数。
{%g%}
由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。
{%endg%}
浅复制和深复制
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
例如,sports是StringBad的实体对象,那么:
将与下面代码等效:
StringBad sailor; sailor.str=sports.str;//@1 sailor.len=sports.len;//@2需要注意的是,@2的复制是正常的,但@1这里复制的并不是字符串,而是一个指向字符串的指针。也就是说,将sailor初始化为sports后,得到的是两个指向同一个字符串的指针。
当析构函数被调用时,将导致同一块内存被释放两次,即释放已经释放的内存,这将导致不确定后果!另一个症状是,试图释放内存两次可能导致程序异常终止。
因此必须定义一个显示复制构造函数,以进行深复制.
如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会深入“挖掘”以复制指针引用的结构.
3. 赋值运算符将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
初始化对象时,并不一定会使用赋值运算符:
这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数。然而,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=运算符时也可能调用赋值运算符。
与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响.
赋值运算符会导致和浅复制一样的效果,将两个指针指向同一块地址!
因此必须显示提供赋值运算符以进行深复制!
但是赋值运算符和复制构造函数有些不同,复制构造函数只存在于初始化过程中,而赋值运算符在初始化和赋值过程中都可能存在。
因此为了处理赋值过程,需要做一些调整:
由于目标对象可能引用了以前分配的数据,所以函数应使用delete[ ]来释放这些数据。
函数应当避免将对象赋给自身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容。
函数返回一个指向调用对象的引用。
StringBad & StringBad::operator=(const StringBad & st) { if(this==&st)//自检,避免给自己赋值 { return *this; } delete [] str;//释放旧字符串 len=st.len; str=new char[len+1]; std::strcpy(str,st.str); return *this;//返回指向调用对象的引用 }