C++拷贝构造函数(复制构造函数)
⼀、拷贝构造函数的形式
复制构造函数是构造函数的⼀种特殊情况。因为类的对象包含各种成员变量,在发⽣拷贝时不能和普通对象⼀样来拷贝,所以我们需要使⽤拷贝构造函数来进⾏对象拷贝。拷贝构造函数只有⼀个参数,参数类型是本类的引⽤。
如果构造函数没有显式定义,那么编译器将会⾃动⽣成拷贝构造函数。⼤多数情况下,其作⽤是实现从源对象到⽬标对象逐个字节的复制,即使得⽬标对象的每个成员变量都变得和源对象相等。
为什么只有⼀个参数?
通常来说,要发⽣拷贝,需要两个参数。但是我们知道在类中存在⼀个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, 如果⼀个构造函数的第⼀个参数是下列之⼀:
1. Quick&
2. const Quick&
3. volatile Quick&
4. 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.浅拷贝
很多前拷贝时候我们在不显式定义拷贝构造函数的情况下,编译器会给我们⾃动产⽣⼀个拷贝构造函数,这个编译器⽣成默认的拷贝构造函数对传递对象给函数参数或者函数返回对象都能很好的完成,这个默认的拷贝构造也叫做浅拷贝。这个构造函数很简单,⼤多数情况下,其作⽤是实现从源对象到⽬标对象逐个字节的复制,即使得⽬标对象的每个成员变量都变得和源对象相等。
那么为什么要⾃⼰显式定义拷贝构造函数呢?我们来看下⾯的代码:
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.深拷贝
上⾯解决问题的拷贝构造函数是⼀种浅拷贝,那么深拷贝和浅拷贝区别在哪呢?
我们先来看看下⾯的代码:
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);
}
也就是在拷贝对象前,先进⾏空间分配。
四、归纳总结
初始化对象的函数有两种,⼀个是构造函数,⼀个是拷贝构造函数(浅拷贝、深拷贝)。以不同的⽅式来初始化⼀个对象时,需要明确调⽤函数是那个,再根据需求来确定使⽤哪种构造函数。在重载赋值运算符时也需要注意拷贝这个问题。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。