真正的危机不是机器人像人一样思考,而是人像机器一样思考。 ——凉宫春日的忧郁

[技术]浅谈重载操作符

前言

重载操作符可以成为强有力的工具,但不可抛弃与客户的契约而滥用,那样只会让程序更难让人理解。

——《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);

我们完全可以把它看做一个函数,不过是函数名有些特殊罢了。

原则

  1. 并不是所有的操作符都能被重载。除了. ,.* ,:: ,? : ,sizeof,typeid这几个运算符不能被重载,其他运算符都能被重载
  2. 重载不能改变该运算符用于内置类型时的函义,程序员不能改变运算符+用于两个int型时的含义。
  3. 运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。这种规定可以防止程序员运用运算符改变内置类型的函义。
  4. 重载不能改变运算符的优先级。
  5. 重载不能改变运算符的结合律。
  6. 重载不能改变运算符操作数的个数。比如+需要两个操作数,则重载的+也必须要有两个操作数。

还有一个很重要的原则

不可改变操作符固有的含义,要保留与客户之间约定的契约,如果你将+重载为减法或是乘法,这会使你的程序更加令人费解

总结

重载操作符是一个强大的工具,运用好重载,可以使你的代码更简洁,明白。

 

posted @ 2017-08-05 19:37  Hzoi_Mafia  阅读(672)  评论(4编辑  收藏  举报
我们都在命运之湖上荡舟划桨,波浪起伏着而我们无法逃脱孤航。但是假使我们迷失了方向,波浪将指引我们穿越另一天的曙光。 ——死神