posts - 38, comments - 0, trackbacks - 0, articles - 0

导航

c++运算符重载

Posted on 2017-11-28 19:21  困或  阅读(...)  评论(...编辑  收藏

1.说明

  [1]重载运算符函数的参数个数,应该与参与这个运算符的运算对象数量一样多,但是如果是成员函数,则参数数量要少一个,因为第一个参数是this。例如:

#include <stdio.h>
#include <stdlib.h>

using namespace std;

class ca
{
public:    
    int value;
    //重载为成员函数格式
    int operator+(const ca &v){
        return this->value + v.value; // 等同于return value+v.value;
    }
};

//重载为非成员函数格式
int operator+(const ca &v1, const ca &v2)
{
    return v1.value + v2.value;
}

int main()
{
    ca a, b;

    a.value = 10;
    b.value = 20;

    printf("a+b:%d\n", a + b);    // 优先用成员函数
    
    return 0;
}

  [2]运算符重载函数的参数至少要有一个类的成员(或者类类型)作为参数,而不能都是内置类型(会导致编译错误)。例如int operator+(int, int)是不行的,因为int是内置类型。内置类型的操作符明显是不能重载的,比如重载了int型的+运算符,那程序其他地方的int加法都会使用重载的函数,这明显是不行的。

  [3]运算符可以像普通函数一样调用。例如上面例子的a+b等同于a.operator+(b);。

  [4]不应该重载的运算符(当然理论是可以重载的,只是重载之后容易出问题):逗号、&、&&、||。

  [5]必须作为成员函数的重载运算符:=、[]、()、->,否则会编译错误。

  [6]重载运算符函数是成员函数时,其实就是运算符左侧的对象调用的这个运算符,所以左侧对象必须是运算符所属类的一个对象(这个是当然的,因为第一个参数是这个类的this指针)。

2.输入输出运算符重载

  [1]首先“<<”和“>>”表示移位操作,然后IO标准库(iostream库)重载了这两个运算符来处理输入和输出,自己重载这两个运算符的目的就是要输出自定义的类。

  [2]如果要与iostream库兼容输入输出操作,则自己重载的时候必须作为非成员函数,这个是因为如果作为成员函数,则左侧的操作对象应该是这个类的对象,这样就不能兼容iostream库的输入输出操作。当然非要重载为成员函数,也是可以编译通过的,只不过是不能兼容标准库iostream的输入输出操作而已。例如:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>

using namespace std;

class ca
{
public:    
    int value;

    //重载<<的目的就是要输出类ca
    ca & operator<<(const ca &v){
        cout << "value:" << v.value << "\n";
        return *this;
    }
};

int main()
{
    ca a, b, c;
    
    a.value = 1;
    b.value = 2;
    c.value = 3;

    //重载函数作为成员函数之后,以后只能这样调用。
    //显然类ca的对象的输出已经和正常的输出不能兼容使用了。
    a << a;
    b << a;
    c << b << a;

    //例如下面这些都是不兼容的,不能编译通过的。
    (cout << "ca info :") << a;
    a << b << "hello.\n";
    return 0;
}

  [3]因为IO运算符通常要访问类的所有成员,正确的做法是把重载的非成员函数声明为类的友元函数。

3.递增和递减运算符重载

  [1]递增和递减一般是改变对象的状态,所以一般是重载为成员函数。

  [2]重载递增递减,一定要和指针的递增递减区分开。因为这里的重载操作的是对象,而不是指针(由于指针是内置类型,指针的递增递减是无法重载的),所以一般情况的递增递减是操作对象内部的成员变量。

  [3]递增和递减分为前置和后置情况,a = ++b;(前置), a = b++;(后置)。因为符号一样,所以给后置版本加一个int形参作为区分,这个形参是0,但是在函数体中是用不到的,只是为了区分前置后置。例如:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>

using namespace std;

// 例如这里的重载递增就是为了增加pos的值
class ca
{
public:    
    int pos;

    //前置递增就是增加当前对象的pos的值,并且返回当前对象
    ca operator++(){
        pos++;
        return *this;
    }

    //后置递增就是增加当前对象的pos的值,并且返回增加pos之前的该对象
    ca operator++(int){
        ca ret = *this;
        ++*this;        //这个会调用上面的函数,其实这里可以换成pos++;
        return ret;
    }
};

int main()
{
    ca a, b;

    a.pos = 1;

    b = a++;
    cout << "b1:" << b.pos << endl;   // b1:1
    
    b = ++a;
    cout << "b2:" << b.pos << endl;   // b1:3
    
    return 0;
}

 4.重载函数调用运算符“()”

  [1]类重载了括号运算符,则该类的对象就可以当成函数一样来使用。另外类里面可以定义数据成员,这样就比普通函数多了一些功能,例如保存一些状态,统计该对象的调用次数等等。

  [2]这种对象常常用作泛型算法的实参,因为有些泛型算法可以提供自定义的比较函数。例如:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

void myprint(const string & s)
{
    printf("my print : %s\n", s.c_str());
    return ;
}

class ca
{
public:
    int call_times;
    ca(){call_times = 0;}
    
    void operator()(const string & s){
        printf("ca print : %s\n", s.c_str());
        call_times++;
    }
};

int main()
{
    vector<string> vs;

    vs.push_back("abc");
    vs.push_back("def");

    //普通函数也是可以代替仿函数的,只是功能不如仿函数强大
    for_each(vs.begin(), vs.end(), myprint);

    ca a;
    for_each(vs.begin(), vs.end(), a);                   //这里要注意,这个是值传递,传的是a的副本
    printf("ca print call times: %d\n", a.call_times);   //所以这个地方的a.call_times还是0

    a("haha");
    a("hehe");
    printf("ca print call times: %d\n", a.call_times);   //现在a.call_times的值就是2了
    
    return 0;
}