前言
重载操作符可以成为强有力的工具,但不可抛弃与客户的契约而滥用,那样只会让程序更难让人理解。
——《c++面向对象高效编程》
背景
XXX:诶,你快过来
博主:蛤蛤蛤?怎么了?
XXX:你教教我那个星号,不是,乘号怎么打啊
博主:乘号怎么打是什么意思啊 喵喵喵
XXX:就是可以让这两个矩阵乘起来啊
博主:。。。你不会是在说重载吧
XXX:对啊= =
博主:等我给你写完this,就来写重载
基本介绍&引入
照例引度娘:
操作符重载,计算机科学概念,就是把已经定义的、有一定功能的操作符进行重新定义,来完成更为细致具体的运算等功能。操作符重载可以将概括性的抽象操作符具体化,便于外部调用而无需知晓内部具体运算过程。
先举个栗子例子:
int a,b,c; a=2333; b=666; c=a*b;
这段代码的意思十分明显,就是声明了两个变量a和b,a赋值为2333,b赋值为666,c赋值为a*b,即1553778。
重要的是,int类型是语言精妙的定义实现过的内置数据类型,我们一写a*b,语言就会很好的计算出它正确的值(你要是爆了int当我没说),但是,假如我们把int换个类型呢?语言还能否正确的计算出我们想要得到的结果呢?
我们打下这样的代码:
struct matrix{ int data[100][100]; }a,b; int main(){ matrix c; c=a*b; }
然后试着编译一下,就会出现:
显然,这样是不行的,那么,我们是否有办法像使用int一样使用我们自己定义的结构体matrix呢?
基本操作
我们继续分析上面的编译信息,显然,编译器正在寻找一个叫'operator*'的东西,这个东西是什么呢?
显然就是我们今天要讲的主题——重载操作符。
operator,关键字,语法:
返回类型 operator 操作符 (参数列表){}
比如说,上面我们想要实现的那个矩阵乘,就可以像这样子实现:
struct matrix{ int data[2][2]; matrix operator*(const matrix &a){ matrix tmp; for(int i=0;i<2;i++) for(int j=0;j<2;j++){ tmp.data[i][j]=0; for(int k=0;k<2;k++) tmp.data[i][j]+=data[i][k]*a.data[k][j]; }
return tmp;
} };
这样我们再写两个matrix相乘,就可以开心地通过编译啦。
我们看到了乘号的重载,那么同样的,我们也可以重载加号,减号,赋值运算符。。。
事实上,除了 . .* :: ?: sizeof typeid 这几个运算符不能被重载,其他运算符都能被重载。
那么,有了*,自然可以有*=,而*=的实现完全可以依靠*的实现
matrix operator*=(const matrix &a){ *this=*this*a; return *this; }
其中,*this为调用该函数(重载操作符其实就是以操作符为函数名的函数)的对象,更多有关this指针的用法可以参考我的上一篇博文
如何定义与使用
我们刚才已经见到了一种定义方法——将重载函数声明为成员函数,那么,还有其他定义重载的方法吗?
当然有。
我们完全可以把运算符重载函数声明为非成员函数,而此时使用最多的是友元函数。
举个栗子例子:
struct point{ double x,y; inline friend bool operator<(const point &a,const point &b){ return a.x==b.x?a.y<b.y:a.x<b.x; } }; inline point operator-(const point &a,const point &b){ return (point){a.x-b.x,a.y-b.y}; }
显然,在这里,一个运算符重载函数被声明为了(非成员)友元函数,另一个则是非成员函数。
进阶
现在,我们已经可以进行简单的重载了,但是,还有一些需要注意的特殊操作符
++
自加操作符
我们知道,自加操作符有前置与后置两种用法,区别如下:
int a,b,c,d; a=2333; b=2333; c=a++;//运行完后,a为2334,c为2333 d=++b;//运行完后,b和d都为2334
那么显然这两种操作是不一样的(令人窒息的操作)。那么如何进行这两种重载呢?
前置:返回值类型& operator++()
后置:返回值类型& operator++(int)
注意,为了区分前置++与后置++的区别,需要在参数后增加一个"int"以示区分。含有"int"的重载方式为后置++,否则为前置++。前置--与后置--类似用法。
举个栗子例子:
struct Int{ int data; Int& operator++();//前置 Int& operator++(int);//后置 };
实现就可以根据自己需要进行了。
流输入输出>>与<<
我们知道,想输入输出一个int,是十分简单的事,比如:
int a; cin>>a; a+=666; a*=2333; cout<<a;
但是,假如我们写下这个呢?
struct matrix{ blabla... }a; int main(){ cout<<a; }
显然是不可以的,所以我们需要重载。
这里,我们不是要重载matrix的什么,而是重载istream&ostream里的某些东西。
我们知道,cin是输入流对象,cout是输出流对象,所以我们要重载的对象是cin的>>与cout的<<
以matrix(矩阵)为例,我一般的写法是:
struct matrix{ int data[100][100]; }; istream& operator>>(istream &in,matrix &x){ for(int i=0;i<100;i++) for(int j=0;j<100;j++) in>>x.data[i][j]; return in; } ostream& operator<<(ostream &out,matrix &x){ for(int i=0;i<100;i++){ for(int j=0;j<100;j++) out<<x.data[i][j]<<' '; out<<endl; } return out; }
这样就很健康啦
WARNING
有约定需要注意:
流对象不允许复制,所以只能返回引用
原理
事实上,我们在写
struct Int{
blabla...
}
Int a,b,c; a=2333; b=666; c=a+b;
实际上是调用了
c=a.operator+(b);
我们完全可以把它看做一个函数,不过是函数名有些特殊罢了。
原则
- 并不是所有的操作符都能被重载。除了. ,.* ,:: ,? : ,sizeof,typeid这几个运算符不能被重载,其他运算符都能被重载
- 重载不能改变该运算符用于内置类型时的函义,程序员不能改变运算符+用于两个int型时的含义。
- 运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。这种规定可以防止程序员运用运算符改变内置类型的函义。
- 重载不能改变运算符的优先级。
- 重载不能改变运算符的结合律。
- 重载不能改变运算符操作数的个数。比如+需要两个操作数,则重载的+也必须要有两个操作数。
还有一个很重要的原则
不可改变操作符固有的含义,要保留与客户之间约定的契约,如果你将+重载为减法或是乘法,这会使你的程序更加令人费解
总结
重载操作符是一个强大的工具,运用好重载,可以使你的代码更简洁,明白。