参考:http://www.weixueyuan.net/view/6383.html
总结:
重载赋值操作符同重载类的是拷贝构造函数的原因是一样,将一个对象拷贝给另一个对象,同时当类中存在指针类型的成员变量时,会出现漏洞。
赋值操作符“=”可以用来将一个对象拷贝给另一个已经存在的对象。
如果我们没有在类中显式定义拷贝构造函数,也没有重载赋值操作符,则系统会为我们的类提供一个默认的拷贝构造函数和一个赋值操作符。
当然,如果在类中并没有包含需要动态分配内存的指针成员变量时,我们使用系统提供的默认拷贝构造函数和赋值操作符也就可以了,无需再自己多此一举的重新定义和重载一遍的。否则必须重载。
赋值操作符“=”可以用来将一个对象拷贝给另一个已经存在的对象。如果我们重新定义了一种新的数据类型,比如说复数类,那么我们就需要重载一下赋值操作符,使之能够满足我们的赋值需求。当然拷贝构造函数同样也会有此功能,拷贝构造函数可以将一个对象拷贝给另一个新建的对象。如果我们没有在类中显式定义拷贝构造函数,也没有重载赋值操作符,则系统会为我们的类提供一个默认的拷贝构造函数和一个赋值操作符。前面在介绍类的相关知识时已经提到,系统为我们提供的默认的拷贝构造函数只是将源对象中的数据一一拷贝给目标对象,而系统为类提供的赋值操作符也是这样的一种功能。
例1:
complex c1(4.3, -5.8); complex c2; c2 = c1; cout<<c1<<endl; cout<<c2<<endl;
利用前面我们定义的complex类,我们先定义了两个complex类的对象c1和c2,c1对象通过带参构造函数初始化,之后用c1来初始化c2,最后输出这两个复数类对象。在前面定义复数类时我们并未定义拷贝构造函数,也没有重载过赋值操作符,但是在例1中“c2 = c1”并未有语法错误,并且根据函数输出结果也可以得知可以完成我们所需要的赋值操作。这是因为系统默认为类提供了一个拷贝构造函数和一个赋值操作符,而数据一对一的拷贝也满足我们复数类的需求了。
在前面介绍拷贝构造函数时我们提到过这种系统提供的默认拷贝构造函数有一定缺陷,当类中的成员变量包含指针的时候就会有问题,会导致一些意想不到的程序漏洞,此时则需要重新定义一个拷贝构造函数,同样的此时系统提供的赋值操作符也已经不能满足我们的需求了,必须要进行重载。在前面介绍拷贝构造函数那一节中,我们已经详细分析了系统提供的默认拷贝构造函数遇到指针成员变量时带来的风险,其实在直接使用系统默认提供的赋值操作符同样会有此种风险,在此我们将不再重新分析这一问题,而只是将前面的示例再次拿过来,并且在程序中补上赋值操作符重载函数。
例2:
#include<iostream> using namespace std; class Array { public: Array(){length = 0; num = NULL;}; Array(int * A, int n); Array(Array & a); Array & operator= (const Array & a); void setnum(int value, int index); int * getaddress(); void display(); int getlength(){return length;} private: int length; int * num; }; Array::Array(Array & a) { if(a.num != NULL) { length = a.length; num = new int[length]; for(int i=0; i<length; i++) num[i] = a.num[i]; } else { length = 0; num = 0; } } //重载赋值操作符 Array & Array::operator= (const Array & a) { if( this != &a ) { delete[] num; if(a.num != NULL) { length = a.length; num = new int[length]; for(int i=0; i<length; i++) num[i] = a.num[i]; } else { length = 0; num = 0; } } return *this; } Array::Array(int *A, int n) { num = new int[n]; length = n; for(int i=0; i<n; i++) num[i] = A[i]; } void Array::setnum(int value, int index) { if(index < length) num[index] = value; else cout<<"index out of range!"<<endl; } void Array::display() { for(int i=0; i<length; i++) cout<<num[i]<<" "; cout<<endl; } int * Array::getaddress() { return num; } int main() { int A[5] = {1,2,3,4,5}; Array arr1(A, 5); arr1.display(); Array arr2(arr1); arr2.display(); arr2.setnum(8,2); arr1.display(); arr2.display(); cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl; arr1 = arr2; arr1.display(); arr2.display(); arr2.setnum(9,3); arr1.display(); arr2.display(); cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl; return 0; }
在这个例子中我们以类成员函数的形式重载了赋值操作符,因为前面已经介绍过该程序了,下面就直接来看主函数。主函数中前半部分和之前介绍拷贝构造函数时是相同的,这个我们也忽略过去。直接从arr1 = arr2语句开始看起。这个语句就会调用类中的操作符重载函数,我们可以将这一语句理解为:
arr1.operator=( arr2 );
然后就会执行赋值操作符重载函数的函数体中的代码,在该函数体中我们为arr1重新开辟了一个内存空间,因此就可以规避arr1和arr2中的num指向同一块存储区域的风险。如此一来使用系统默认提供的赋值操作符所带来的风险就可以避免了。在这之后的语句中,我们还修改了arr2中的数据,但是这样的修改并没有影响到arr1,可见确实将风险给化解了。
当然,如果在类中并没有包含需要动态分配内存的指针成员变量时,我们使用系统提供的默认拷贝构造函数和赋值操作符也就可以了,无需再自己多此一举的重新定义和重载一遍的。