C++拷贝构造函数和=赋值运算符详解
首先明确一点:
系统已经提供了默认的 拷贝构造函数 和 =复制运算符。 即所谓的浅拷贝。
但有时,我们必须提供自己重写。一般是在有指针的情况下重写。
举个简单的例子,没有指针,其实不必重写,只是为了演示:
[cpp]
class Fraction{
private:
int fenmu; //分母
int fenzi; //分子
public:
Fraction(int x,int y){
fenzi = x;
fenmu = y;
}
Fraction(){}
Fraction(const Fraction & fr);
Fraction & operator=(Fraction& p);
void display(){
cout << fenmu << " " << fenzi;
}
};
Fraction::Fraction(const Fraction & fr){
cout << "test: use copy" << endl;
fenmu = fr.fenmu;
fenzi = fr.fenzi;
}
Fraction & Fraction::operator=(Fraction& fr){
if(this == &fr)
return *this;
fenmu = fr.fenmu;
fenzi = fr.fenzi;
cout << "test use =" << endl;
return *this;
}
int main(){
Fraction f(1,2);
Fraction f2(f); //use copy
//f2.display();
Fraction f3 = f2; // use copy
Fraction f4,f5;
f5 = f4 = f3; // use =
//f5.display();
return 0;
}
output:
[cpp]
test: use copy
test: use copy
test use =
test use =
如果有指针,则需要深拷贝:
[cpp] view plaincopyprint?
#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;
}
通过拷贝相同类的另一个对象的状态来初始化一个对象。
使用:当通过数值传递,通过数值返回,或明确拷贝一个对象
赋值运算符
返回什么:一般通过引用返回*this,就是说用户自己定义的类的赋值遵守和内部类型相同的约定,赋值也可用作表达式,也即能够级联
自我赋值语法上没有任何错误,但如果你没有很好的实现赋值运算符,那么灾难可能就在等着你。
所以你必须确保自我赋值无害,也就是要加入自我赋值的检测
[cpp]
CAssignment& CAssignment::operator=(const CAssignment& a)
{
if( this == &a )
return *this;
//….赋值该作的工作
}
保证赋值运算符只有两种结果:完全成功、原封不动留下对象并抛出异常
[cpp]
CAssignment& CAssignment::operator=(const CAssignment& a)
{
if( this == &a )
return *this;
CTemp* t = new CTemp;
//…..
delete _tmp;
_tmp = t;
return *this;
}
派生类使用赋值运算符
派生类中的赋值运算符首先调用其直接基类赋值运算符(对声明在基类里的成员对象赋值),然后再调用它的成员对象的赋值运算符(改变在派生类里声明的那些成员对象)。这些赋值通常应该与基类和成员对象在该类的定义里出现的次序相同。
[cpp]
CDerived& CDerived::operator=(const CDerived& r)
{
CBase::operator=(r);
_c = r._c;
return *this;
}
重载赋值运算符的正确形式:
c++的设计者stroustrup下了很大的功夫想使用户自定义类型尽可能地和内部
类型的工作方式相似。为此他做了很多努力(如重载运算符,写类型转换函
数和拷贝构造函数,等等)。而你也该继续做下去。
让我们看看赋值。用内部类型的情况下,赋值操作可以象下面这样链起来:
int w, x, y, z; w = x = y = z = 0;
所以,你也应该可以将用户自定义类型的赋值操作链起来:
CString w, x, y, z; // MFC “自定义”的类型
w = x = y = z = "hello";
因为赋值运算符的结合性天生就是由右向左,所以上面的赋值可以解析为:
w = (x = (y = (z = "hello"))); <=>
w.operator=(x.operator=(y.operator=(z.operator=("hello"))));
这个格式说明了w.operator=, x.operator=和y.operator=的参数是前一个
operator=调用的返回值。所以operator=的返回值必须可以作为一个输
入参数被函数自己接受。一般情况下operator=输入应该是类对象或
类对象的引用,从效率来说后者好过前者 ,所以输入和返回的都应
是类对象的引用。
又因为有
int x, y,z; (x = y) = z ;
所以如下语句也应正确
CString x, y, z; ( x = y) = z;
那么operator=的返回不能是const(因为常量不能赋左值)
又有 www.2cto.com
CString x; x = “Hello”; <=>
const CString temp(“Hello”); //产生临时对象,为什么是const
x = temp;
所以为保证上面的语句成立, operator=的输入应是const
所以最好的实现是 T& operator=(const T&);