1. 基本概念
所谓重载,就是重新赋予新的含义。不仅函数可以重载,运算符也可以重载。
a. 运算符重载的本质是一个函数。
b. 实现运算符重载是通过operator关键字实现的,运算符重载可以通过类的成员函数和类的友元函数来实现。
区别在于成员函数具有this指针,通过对象调用来传递参数,友元函数没有this指针。
c. 重载为类的成员函数时,类的this指针会被绑定到运算符的左侧运算对象,因此成员运算符重载函数的显示参数比运算符对象总数少一个,
即也比全局的运算符重载函数少一个参数。也就是说要想重载为成员函数,运算符的左操作数必须是该类类型的对象。
d. 重载为全局的函数,意味着失去了运算符左侧类型的限制。对于二元运算符,全局函数第一个参数即为运算符左侧对象,第二个参数为右侧对象。
e. 不论重载为友元函数还是成员函数,运算符的调用方法相同,但传递参数的方式不同,实现的代码不同,应用场合也不同。
接下来我们来推演一下运算符重载函数进化为成员函数的过程:
1)相加函数作为普通函数
class Complex { public: int a, b; public: Complex(int a = 0, int b = 0) { this->a = a; this->b = b; } }; Complex Add(Complex &c1, Complex &c2) { Complex tmp(c1.a + c2.a, c1.b + c2.b); return tmp; } int main() { Complex c1(1, 2), c2(3, 4); // 1 普通函数 Complex c3 = Add(c1, c2); return 0; }
2)相加函数作为友元函数
class Complex { public: int a, b; friend Complex operator+(Complex &c1, Complex &c2); public: Complex(int a = 0, int b = 0) { this->a = a; this->b = b; } }; Complex operator+(Complex &c1, Complex &c2) { Complex tmp(c1.a + c2.a, c1.b + c2.b); return tmp; } int main() { Complex c1(1, 2), c2(3, 4); // 2 友元函数,这里也可以写成:Complex c3 = operator+(c1, c2) Complex c3 = c1 + c2; return 0; }
3)相加函数作为类成员函数
class Complex { public: int a, b; Complex operator+(Complex &c2) { Complex tmp; tmp.a = this->a + c2.a ; tmp.b = this->b + c2.b ; return tmp; } public: Complex(int a = 0, int b = 0) { this->a = a; this->b = b; } }; int main() { Complex c1(1, 2), c2(3, 4); // 3 成员函数,这里也可以写成:Complex c3 = c1.operator+(c2) Complex c3 = c1 + c2; return 0; }
2. 什么时候运算符重载函数需要返回一个引用?
其实本质就是要判断:你需要返回的是对象本身还是返回一个新的对象。
3. 重载>>和<<运算符
1)istream 和 ostream 是 C++ 的预定义流类
2)cin 是 istream 的对象,cout 是 ostream 的对象
3)运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
4)运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
5)用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型
我们首先来观察一下它的调用形式:
// cout.operator<<(c1).operator<<("abcd"); cout << c1 << "abcc";
很明显,如果要重载为成员函数,必须重载为运算符左边对象类型的成员函数,因此得拿到cout这个类的源码。
故只能使用友元函数重载>>或者<<运算符。结论:当无法修改左操作数的类时,使用友元函数进行重载。
具体代码如下:
ostream& operator<<(ostream &out, Complex &c1) { out << c1.a << " + " << c1.b << "i " << endl; return out; }
为什么要返回引用呢?
因为对于连续输出的情况来讲,操作的都是同一个对象,比如上面先输出c1,然后再输出abcc,都是在同一个cout对象上操作。
4. 重载赋值运算符=
=运算符必须重载为成员函数,理由如下:
1)一个c++类,如果没有为其定义赋值操作符重载函数,编译器也会隐式的定义,这样倘若再定义全局的赋值运算符重载函数,将会发生二义性。
2)如果赋值操作符可以作为全局函数重载的话,可能会出现表达错误的语句,如int operator=(int a, cls &b);
这样重载之后,语句2 = a; 表述也是正确的,但却是明显的语法错误。
Complex& operator=(const Complex& c) { if (*this != c) { a = c.a; b = c.b; } return *this; }
返回引用,很明显复值后返回的当然还是对象自身。
5. 重载[]操作符
设 x 是类 X 的一个对象,则表达式 x[y] 可被解释为 x.operator[](y)
[]是二元运算符,且必须重载为成员函数。如果重载为全局的函数,很容易写出如下错误的代码:
cls& operator[](int dat, cls& c) { //... return c; } int main() { cls c(1, 'h'); 6[c]; // []是二元运算符,左侧是6,右侧是c return 0; }
因为[]操作符重载函数是全局的,也就是没有了该函数的左操作数是this指针的限制,程序员可以任意定义左操作数的类型,
类似的,就会出现6=c, 6(c), 6->c的代码,显然这样的代码是错误的。
故:C++中不能用友员函数重载的运算符有:= () [] ->
6. 重载()操作符
设 x 是类 X 的一个对象,则表达式 x(arg1,arg2,… ) 可被解释为 x.operator()(arg1,arg2,…)。
()也是二元运算符。
class F { public: double operator()(double x, double y) { return x * x + y * y ; } }; int main() { F f; cout << f(5.2, 2.5) << endl; // f.operator()(5.2, 2.5) return 0; }