重载赋值运算符
1、为什么要重载赋值运算符?
预定义的赋值运算符"="要求左右两边的操作数类型是匹配的或者至少是兼容的。有时希望赋值运算符两边的操作数即使类型不兼容也能成立。此时可以通过重载赋值运算符来完成这种需求。
2、重载赋值运算符的特点
- C++规定,只能将赋值运算符重载为成员函数类型。
3、举例
class String {
private:
char * str;
public:
String ():str(new char[1]) { str[0] = 0;}//无参构造函数
const char * c_str() { return str; };//普通成员函数
String & operator = (const char * s);//重载赋值运算符的符号成员函数
String( ) { delete [] str; } //析构成员函数
};
//重载“=”以使得 obj = “hello”能够成立
String & String::operator = (const char * s)
{
delete [] str;
str = new char[strlen(s)+1];
strcpy( str, s);
return * this;
}
int main()
{
String s;
s = "Good Luck," ; //等价于 s.operator=("Good Luck,"); 左边是String类类型,右边是string类型。赋值符号两边虽然类型不匹配,但是由于重载了赋值运算符,它能够调用符号函数,使得编译能够通过。
cout << s.c_str() << endl;
// String s2 = "hello!"; 这条语句要是不注释掉就会出错。因为这是初始化语句,而不是赋值语句。重载赋值运算符只在赋值语句中起作用。
s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!"); 这是属于赋值语句。
cout << s.c_str() << endl;
return 0;
}
/*
输出:
Good Luck,
Shenzhou 8!
*/
4、该例中存在的问题
上述代码中执行:
String s2 = "hello!";
这条语句会发生错误,因为这里的等号并不是赋值的意思,而是初始化的意思。那么如何解决这种定义时初始化的问题呢?
解决方案:
可以定义一个类型转换构造函数:
String(char* s){
str = new char[strlen(s)+1];
strcpy( str, s);
}
这样,在这行这条语句时,运行的将会是类型转换构造函数。
5、为什么将赋值运算符函数的返回值写成引用类型?
首先考虑可选的类型:
- void
- String
- String
我们写符号函数应该尽量符合符号原有的用法习惯。
(1)写成void的带来的问题:
a = b = c;
如果赋值运算符的返回类型为空,那么执行该语句就会出现错误。首先,运算符重载并不改变运算符的优先级和结合性,由于赋值运算符的右结合性,因此先执行b=c,此时能够正确执行,但是执行的结果为void类型,将一个空类型赋值给String类型的a对象,显然会导致错误。但是按照我们原有的对赋值运算符的用法,它是可以被用来连等的。解决这个问题的方法是,将返回值类型设置为String,或者String&。
(2)写成String带来的问题:
(a=b)=c;
解释一:
此时由于括号“()”改变了结合顺序,所以先执行a=b,结果是将b的值赋值给a,这里没有问题。但是,因为函数的返回值类型只有为引用时返回得到的才是左值,为其他类型时返回得到是右值。右值是不可以位于赋值运算符的左侧的,因此该语句会导致结果异常。
解释二:
此时由于括号“()”改变了结合顺序,所以先执行a=b,结果是将b的值赋值给a,这里没有问题。该函数表达式调用赋值符号函数,返回一个String类型的临时变量。然后再次调用赋值符号函数,将c的值赋值给该临时变量。因此,输出a的话得到的结果仍是b的值。
这两条解释的区别在于,赋值符号函数返回的结果是左值,还是非左值。可以在编译器上跑一下是哪种结果。此时可以把=看成是一个函数,这个函数返回值是左值,那么赋值符号返回的就是左值。这个函数返回值是右值,那么赋值符号返回的就是右值。在《C++ primer》一书中提到,函数返回值只有为引用类型时,返回结果才是左值,而这里显然不是左值。所以按照这样分析,应该是解释一成立。
实际上,无论这两条解释的哪一条成立,导致的结果是:我们不想看到的效果。
6、实验验证上述解释
- 将赋值符号函数返回值改为void类型,在Codeblock上面跑此代码。确实如同上面预想的一样。在执行连等的时候会发生意外。
- 将赋值符号函数返回值改为String类型,在Codeblock上面跑此代码。和上述解释中任何一个都不符合,实验的结果显示,将赋值符号函数返回值更改为String类型后,甚至连一个等号的赋值都会发生意外。代码与结果如下(代码较上述代码有所优化):
#include<iostream>
#include<cstring>
using namespace std;
class String {
private:
char * str;
public:
String ():str(NULL) { }//无参构造函数
const char * c_str() const{ return str; };//普通成员函数
String operator = (const char * s);//重载赋值运算符的符号成员函数
~String( ) {
if(str)
delete [] str;
} //析构成员函数
};
//重载“=”以使得 obj = “hello”能够成立
String String::operator = (const char * s)
{
cout <<"operator = " << endl;
if(str)
delete [] str;
if(s){
cout <<"if s " << endl;
str = new char[strlen(s)+1];
strcpy( str, s);
}else{
str=NULL;
}
cout << (*this).c_str() << endl;
return *this;
}
int main()
{
String s,s1,s2;
s = "Good Luck,") ;
cout <<"s1"<< s1.c_str() << endl;
s1 = "shenzhou8";
cout <<"s1:"<< s1.c_str() << endl;
s2=s1=s;
cout <<"s2:"<< s2.c_str() << endl;
return 0;
}