看c++面向对象高级开发的课时,操作符重载的问题。
出自于c++ primer 5e的一句话:
赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员(函数)。
赋值运算符。
我们知道一个c++类,程序员如果没有为其定义了赋值操作符重载函数,编译器也会隐式的定义,这样倘若再定义全局赋值运算符重载函数,将会发生二义性。即使编译器允许这样的定义手法,在调用的时候也编译不过:
cls& operator=(cls& c1, const cls& c2)
{
//...
return c1;
}
class cls
{
public:
int m_a;
char m_c;
cls(int a, char c) : m_a(a), m_c(c) {}
};
int main(void)
{
cls c1(1, 2), c2(3, 4);
c1 = c2; //调用的是编译器隐式定义的operator=()还是程序员显示定义的全局的operator=()
return 0;
}
操作符重载函数规定是类的成员函数,有一个至关重要的特点:类的this指针会被绑定到运算符的左侧运算对象,成员运算符函数的显示参数比运算符对象总数少一个。也就是说上文提到这些运算符的左操作数必须是该类类型的参数,换句话说,假设c++编译器允许[]操作符重载函数是全局的,那么程序员完全可以写出:
cls& operator[](int dat, cls& c)
{
//...
return c;
}
int main(void)
{
cls c(1, 'h');
6[c];//c++编译器允许[]操作符重载函数是全局的
return 0;
}
因为[]操作符重载函数是全局(友元)的,也就是没有了该函数的左操作数是this指针的限制,程序员可以任意定义左操作数的类型,类似的,就会出现6=c, 6(c), 6->c的代码,显然这样的代码是错误的。
不仅如此,假设cls类定义了转换构造函数:
cls::cls(int i);
那么 operator的函数原型大可以是:
cls& operator[](cls& c1, cls& c2)
{
//...
return c1;
}
看似正确,但是若用户利用转换构造函数的隐式类型转换调用该函数:
int main(void)
{
cls c(1, 'h');
6[c];
return 0;
}
同样编译通过,显然这还是错误的。因此,c++编译器对这些操作直接视为语法问题。
string类的加号操作符重载函数
另外我们可以推测string类的加号操作符重载函数是有两个版本的。如下正常运行的代码:
std::string s1 = "hello";
const char* s2 = "world";
string s3 = s1 + "world";
string s4 = s2 + s1;
代码中的两个加法操作分别执行成员函数的
string& operator+(const string& s);
和友元全局函数的
string& opreator+(std::string s1, std::string& s2);
因为假设二者都是调用成员函数的operator+(),那么第2个加法语句等价于
s4 = s2.operator+(s1);
然而s2是const char*类型,根本没有成员函数。