当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值构造函数。
当没有重载赋值构造函数(赋值运算符)时,通过默认赋值构造函数来进行赋值操作
A a;
A b;
b = a;
注意:这里a,b对象是已经存在的,是用a对象来赋值给b的。
赋值运算符的重载声明如下:
A& operator = (const A& other)
通常大家会对拷贝构造函数和赋值构造函数混淆,这里仔细比较两者的区别:
1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值构造函数时对于一个已经被初始化的对象来进行赋值操作。
1 class A; 2 A a; 3 A b=a; //调用拷贝构造函数(b不存在) 4 A c(a) ; //调用拷贝构造函数 5 6 /****/ 7 8 class A; 9 A a; 10 A b; 11 b = a ; //调用赋值函数(b存在)
2)实现不一样,拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个新对象。赋值构造函数是把一个新的对象赋值给一个原有的对象。
举例:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 class MyStr 6 { 7 private: 8 char *name; 9 int id; 10 public: 11 MyStr():id(0),name(NULL) {} 12 MyStr(int _id, char *_name) //构造函数 13 { 14 cout << "constructor" << endl; 15 id = _id; 16 name = new char[strlen(_name) + 1]; 17 strcpy_s(name, strlen(_name) + 1, _name); 18 } 19 MyStr(const MyStr& str) //拷贝构造函数 20 { 21 cout << "copy constructor" << endl; 22 id = str.id; 23 name = new char[strlen(str.name) + 1]; 24 strcpy_s(name, strlen(str.name) + 1, str.name); 25 } 26 MyStr& operator =(const MyStr& str)//赋值运算符 27 { 28 cout << "operator =" << endl; 29 if (this != &str) 30 { 31 if (name != NULL) 32 delete[] name; 33 this->id = str.id; 34 int len = strlen(str.name); 35 name = new char[len + 1]; 36 strcpy_s(name, strlen(str.name) + 1, str.name); 37 } 38 return *this; 39 } 40 ~MyStr() 41 { 42 delete[] name; 43 } 44 }; 45 46 int main() 47 { 48 MyStr str1(1, "hhxx"); 49 cout << "====================" << endl; 50 MyStr str2; 51 str2 = str1; 52 cout << "====================" << endl; 53 MyStr str3 = str2; 54 return 0; 55 }
结果:
说明:
1、参数
一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用,加const是因为:
(1)我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
(2)加上const,对于const的和非const的实参,函数都能接受;如果不加,就只能接受非const的实参。
用引用是因为:
这样可以避免在函数调用时对实参的一次拷贝,提高了效率。
注意:上面的规定都不是强制的,可以不加const,也可以没有引用。
2、返回值
一般地,返回值是被赋值者的引用,即*this(如上面例1),原因是:
(1)这样在函数返回时避免一次拷贝,提高了效率。
(2)更重要的,这样可以实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。
注意:这也不是强制的,我们甚至可以将函数返回值声明为void,然后什么也不返回,只不过这样就不能够连续赋值了
3、赋值运算符重载函数不能被继承
因为相较于基类,派生类往往要添加一些自己的数据成员和成员函数,如果允许派生类继承积累的赋值运算符重载函数,那么,在派生类不提供自己的赋值运算符重载函数时,就只能调用基类的,但基类版本只能处理基类的数据成员,在这种情况下,派生类自己的数据成员怎么办?所以,C++规定,赋值运算符重载函数不能被继承。
4、赋值运算符重载函数要避免自赋值
对于赋值运算符重载函数,我们要避免自赋值(即自己给自己赋值)的发生,一般地,我们通过比较赋值者与被赋值者的地址是否相同来判断两者是否是同一对象(如例中的if(this != &str)一句)。避免自赋值的意义是:
(1)提高效率,显然,自己给自己赋值完全是毫无意义的无用功,特别地,对于基类数据成员间的赋值,还会调用基类的赋值运算符重载函数,开销是很大的。如果我们一旦判定是自赋值,就立即return *this,会避免对其它函数的调用。
(2)如果类的数据成员中含有指针,自赋值有时会导致灾难性的后果。对于指针间的赋值(注意这里指的是指针所指内容间的赋值,这里假设用_p给p赋值),先要将p所指向的空间delete掉(为什么要这么做呢?因为指针p所指的空间通常是new来的,如果在为p重新分配空间前没有将p原来的空间delete掉,会造成内存泄露),然后再为p重新分配空间,将_p所指的内容拷贝到p所指的空间。如果是自赋值,那么p和_p是同一指针,在赋值操作前对p的delete操作,将导致p所指的数据同时被销毁。那么重新赋值时,拿什么来赋?
所以,对于赋值运算符重载函数,一定要先检查是否是自赋值,如果是,直接return *this。
转载自: