C++允许我们为我们的类重定义或重载大部分C++内置的运算符。这样我们的类就可以通过这些运算符进行一些操作。比如加减乘除,赋值比较等。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。
与其他函数一样,重载运算符有一个返回类型和一个参数列表。
比如我们要给复数类Complex重载加法运算符:
Complex operator+(Complex& a) //成员函数
这是个定义为成员函数的例子,如果我们要定义为非成员函数,则应有两个参数。
Complex operator+(Complex& a,Complex& b) //非成员函数
其中参数为进行该运算时的变量类型,函数返回值是运算的结果。比如例子中的复数相加后那么返回的当然也应该是一个复数。
下面是演示代码:
成员函数实现:
#include<iostream> using namespace std; class Complex { double real,imag; public: Complex(double r,double m) //构造函数 { real=r; imag=m; } Complex operator+(Complex& a) //成员函数重载+运算符 { return Complex(real+a.real,imag+a.imag); } void show() { cout<<real<<' '<<imag<<'i'<<endl; } }; int main() { Complex Cmp1(1,2); Cmp1.show(); Complex Cmp2(3,4); Cmp2.show(); Complex Cmp3=Cmp1+Cmp2; //将Cmp1+Cmp2的结果赋给Cmp3 Cmp3.show(); return 0; }
友元函数实现:
#include<iostream> using namespace std; class Complex { double real,imag; public: Complex(double r,double m) //构造函数 { real=r; imag=m; } friend Complex operator+(Complex& a,Complex& b) //非成员函数的重载+运算符 { return Complex(a.real+b.real,a.imag+b.imag); } void show() { cout<<real<<' '<<imag<<'i'<<endl; } }; int main() { Complex Cmp1(1,2); Cmp1.show(); Complex Cmp2(3,4); Cmp2.show(); Complex Cmp3=Cmp1+Cmp2; //将Cmp1+Cmp2的结果赋给Cmp3 Cmp3.show(); return 0; }
输出结果:
1 2i 3 4i 4 6i
没错就是这么简单。
但是其实程序在编译时,成员函数版的重载编译器将 Cmp1+Cmp2 替换为了
Cmp1.operator+(Cmp2)
友元函数版的重载替换为了
operator+(Cmp1,Cmp2)
我们将程序改一下。
int main() { Complex Cmp1(1,2); Cmp1.show(); Complex Cmp2(3,4); Cmp2.show(); Complex Cmp3= Cmp1.operator+(Cmp2); //非成员函数版应为 Complex Cmp3= operator+(Cmp1,Cmp2); Cmp3.show(); return 0; }
输出结果相同
可以看到重载运算符其实没多高端,就是调用了个函数,和一般的函数没太大区别。
接下来重载一下<<运算符让这个类可以用cout直接输出。
如果我们定义成成员函数,那么最后调用的就是上面那个样子,就是Cmp1.operator<<(参数)了。
如果我们要用cout输出,应该是cout.operator<<(Cmp)
这样看来我们是要定义成ostream类的成员函数了,但这显然是不行的,我们不能改ostream的源码,而且也不符合 面向对象 的要求。
所以我们使用第二种,定义为友元函数。
首先当然是要定义成Complex类的友元函数,但是ostream类的对象也是这个运算符的参数,
需不需要定义成ostream类的友元函数呢?
定义为Comlex的友元函数是因为输出时,输出的是Complex类的私有成员,需要友元函数才能访问,
但是不需要访问ostream类的私有成员,所以ostream只需要作为参数传入即可。
下面是完整的代码实现:
#include<iostream> using namespace std; class Complex { double real,imag; public: Complex(double r,double m) //构造函数 { real=r; imag=m; } Complex operator+(Complex& a) //重载加法,返回Complex类的对象 { return Complex(real+a.real,imag+a.imag); } friend ostream& operator<<(ostream& os,Complex& a) //重载<<运算符 { os<<a.real<<' '<<a.imag<<'i'; return os; } }; int main() { Complex Cmp1(1,2); Complex Cmp2(3,4); cout<<Cmp1<<endl<<Cmp2<<endl; //cout的连续输出 Complex Cmp3=Cmp1+Cmp2; cout<<Cmp3<<endl; return 0; }
其中重载的<<运算符的返回值类型是ostream& 是因为我们用cout输出时,经常会有
cout<<变量1<<变量2<<.........<<endl;这样的操作。
就像26行那样
而且使用<<运算符的也可能是ostream的派生类的对象,所以我们要返回引用,使得前后一致。
一些注意事项
- 要清楚运算符的参数是什么,顺序是什么,以此来写重载运算符的函数,比如我们重载<<运算符的时候
- 想清楚该操作符需要什么样的返回值,比如加法就返回一个对象,<<返回一个ostream&
最后附上可重载/不可重载的运算符表
下面是可重载的运算符列表:
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),--(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
下面是不可重载的运算符列表:
- .:成员访问运算符
- .*, ->*:成员指针访问运算符
- :::域运算符
- sizeof:长度运算符
- ?::条件运算符
- #: 预处理符号