一、拷贝构造函数的形式
复制构造函数是构造函数的一种特殊情况。因为类的对象包含各种成员变量,在发生拷贝时不能和普通对象一样来拷贝,所以我们需要使用拷贝构造函数来进行对象拷贝。拷贝构造函数只有一个参数,参数类型是本类的引用。
如果构造函数没有显式定义,那么编译器将会自动生成拷贝构造函数。大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。
为什么只有一个参数?
通常来说,要发生拷贝,需要两个参数。但是我们知道在类中存在一个this指针,所以其中一个参数就是this指针指向的对象,而这个对象被隐式的定义了,所以在拷贝构造函数中我们只需要给出用来初始化this指针指向对象的另一个对象。听起来是不是有点绕,简单来说,当我用一个对象去初始化另一个对象时,拷贝构造函数会发生调用,调用这个函数的对象作为this指针指向的对象,而用来初始化这个对象的对象就需要作为实参传递给拷贝构造函数,所以在拷贝构造函数中我们需要一个对象的形参来接收它。
需要注意的是,这里所说的一个参数并不真的仅仅只局限于一个参数。对象拷贝的过程是一对一的过程,所以这里的参数指的是类所定义的对象。
-
class Quick{
-
public:
-
Quick(
int rear=
1,
int imag=
1)
-
{
-
_rear = rear;
-
_imag = imag;
-
}
-
Quick(
const Quick& ret)
-
{
-
_rear = ret._rear;
-
_imag = ret._imag;
-
cout <<
"copy" <<
endl;
-
}
-
-
private:
-
int _rear;
-
int _imag;
-
};
-
int main()
-
{
-
Quick s1;
-
Quick s2(s1);
//调用拷贝构造函数对s2进行初始化
-
return
0;
-
}
对于一个类Quick, 如果一个构造函数的第一个参数是下列之一:
- Quick&
- const Quick&
- volatile Quick&
- const volatile Quick&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
例如Quick(const Quick&,int m=3)也是拷贝构造函数
为什么参数类型是本类的引用?
当调用拷贝构造函数时,假设以值的方式进行传递,将类对象的值传递过去,首先会进行临时拷贝,因为类对象的拷贝要调用临时拷贝函数,又因为使用的是值传递,所以会再次发生临时拷贝,从而进行无限递归。所以为了避免这种情况发生,请使用类的引用方式。
二、何时调用拷贝构造函数
调用拷贝构造函数一共有三种情况:
1.当用一个对象去初始化同类的另一个对象时,会引发拷贝构造函数被调用
-
Quick s1;
-
Quick s2(s1);
这里需要注意的是
Quick s1; Quick s2; s2=s1;
上面第三条语句是赋值语句,不是初始化语句,因为左操作数已经是一个已经定义的变量,所以并不会调用拷贝构造函数。
2.如果函数fun的参数是类Quick的对象,那么当fun被调用时,类Quick的拷贝构造函数将被调用
-
int main()
-
{
-
fun(Quick ret);
-
return
0;
-
}
3.如果函数的返冋值是类Quick的对象,则函数返冋时,类Quick的拷贝构造函数被调用
-
Quick Fun() {
-
Quick ret(2,3);
-
return ret;
-
}
三、浅拷贝和深拷贝
1.浅拷贝
很多前拷贝时候我们在不显式定义拷贝构造函数的情况下,编译器会给我们自动产生一个拷贝构造函数,这个编译器生成默认的拷贝构造函数对传递对象给函数参数或者函数返回对象都能很好的完成,这个默认的拷贝构造也叫做浅拷贝。这个构造函数很简单,大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。
那么为什么要自己显式定义拷贝构造函数呢?我们来看下面的代码:
-
class Hyottoko {
-
public:
-
Hyottoko()
-
{
-
m_count++;
-
}
-
-
static int getCount()
-
{
-
return m_count;
-
}
-
~Hyottoko()
-
{
-
m_count--;
-
}
-
private:
-
int *m_next;
-
int m_data;
-
static
int m_count;
-
};
-
int Hyottoko::m_count =
0;
-
-
int main()
-
{
-
Hyottoko s1;
//第一个对象
-
Hyottoko s2(s1);
//第二个对象
-
cout << s2.getCount()<<
endl;
-
-
system(
"pause");
-
return
0;
-
}
代码要解决的问题是:获取类定义的对象的个数
在上面的代码中我们没有显式定义拷贝构造函数,使用编译器默认生成的拷贝构造函数,当我们在使用一个对象去初始化另一个对象时会对拷贝构造函数调用。对于调用默认生成的拷贝构造函数初始化的对象并没有对用于对象计数的count进行操作,所以此时虽然有两个对象,但count的值是1,同时当析构函数被调用时,count的值为-1。
这种结果时拷贝构造函数对于静态数据成员没有做出相应的处理所造成的。
如果我们在类中添加显式的拷贝构造函数就可以解决这种问题
-
Hyottoko(
const Hyottoko& data)
-
{
-
m_count++;
-
}
2.深拷贝
上面解决问题的拷贝构造函数是一种浅拷贝,那么深拷贝和浅拷贝区别在哪呢?
我们先来看看下面的代码:
-
class String {
-
public:
-
String(
char*str =
NULL)
-
{
-
m_str =
new
char[
strlen(str)+
1];
-
strcpy(m_str, str);
-
}
-
String(String& str)
-
{
-
strcpy(m_str, str.m_str);
-
}
-
~String()
-
{
-
if (m_str)
-
delete[]m_str;
-
}
-
private:
-
char *m_str;
-
};
-
int main()
-
{
-
String s1("Hello");
-
String s2(s1);
-
-
system(
"pause");
-
return
0;
-
}
使用s1去初始化s2调用拷贝构造函数,我们的目的是将s1中的内容拷贝到s2的空间中,但实际上,使用浅拷贝的方式只是改变了s2的指向,让s2指向s1所指向的空间,这会使得调用析构函数时,同一空间被释放两次,造成错误;而且原来s2指向的空间没有被释放,造成内存泄露。为了解决这种问题我们需要进行深拷贝,修改类中的拷贝构造函数,修改如下:
-
String(String& str)
-
{
-
m_str =
new
char[
strlen(str.m_str)+
1];
-
strcpy(m_str, str.m_str);
-
}
也就是在拷贝对象前,先进行空间分配。
四、归纳总结
初始化对象的函数有两种,一个是构造函数,一个是拷贝构造函数(浅拷贝、深拷贝)。以不同的方式来初始化一个对象时,需要明确调用函数是那个,再根据需求来确定使用哪种构造函数。在重载赋值运算符时也需要注意拷贝这个问题。