重载运算

重载的运算符是具有特殊名字的函数:它们的名字由关键字 operator 和其后要定义的运算符号共同组成。和其他函数一样,重载的运算符也包含返回类型、参数列表以及函数体。对于一个运算符函数来说,它或者是类的成员,或者至少含一个类类型的参数:

//错误:不能为int重定义内置的运算符
int operator+(int, int);

重载运算符函数的参数与该运算符作用的运算对象数量一样多,一元运算符有一个参数,二元运算符有两个。

...
//为了访问ope对象的私有成员,将该运算符函数声明为ope的友元函数
ope& operator+ (ope& lhs, ope& rhs)
{
    lhs.x += rhs.x;
    return lhs;
}

当一个重载的运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显示)参数数量比运算对象的数量少一个。例如上面的 +、+=这两个运算符本来是二元运算符应该有两个显示的参数,但是由于重载运算符函数是成员函数,所以this绑定到左侧运算对象,只有一个参数传入右侧对象。另外,输入输出重载运算符必须是普通的非成员函数。
下面是一些成员函数重载运算符以及输出重载运算符的例子:

#include
using namespace std;
class ope
{
public:

    friend ostream& operator<< (ostream&, ope&);
    friend ope& operator+ (ope& lhs, ope& rhs);

    ope(){}
    ope(int i):x(i){}

    ope& operator+= (ope& rhs){
        x += rhs.x;
        return (*this);
    }

    ope& operator+(ope& rhs)
    {
        return (*this += rhs);
    }

    int getvalue() const{
        return x;
    }
private:
    int x;
};
//重载输出运算符
ostream& operator<< (ostream& out, ope& rhs)
{
    out << rhs.x;
    return out;
}

测试代码为:

#include
#include"operator.h"
int main(int argc,char *argv[])
{
    ope A(2);
    ope B(3);

    std::cout << A+B << std::endl;
    return 0;
}

通常情况下,我们会像内置类型那样使用运算符。我们也可以像调用普通函数一样直接调用运算符函数,先指定函数名字,然后再传入数量正确、类型适当的实参:

...
std::cout << A.operator+=(B) << std::endl;
std::cout << operator+(A, B) << std::endl;
...

通常情况下,不应该重载逗号、取地址、逻辑与和逻辑或运算符。因为逻辑与和逻辑或没办法保留短路求值属性,而逗号和取地址符在C++里已经有了内置含义,为了避免它们的行为异于常态,所以尽量不要重载它们。

重载运算符的返回类型通常情况下应该与其内置版本的返回类型兼容:逻辑运算符关系运算符都应该返回bool,算数运算符应该返回一个类类型的值,赋值运算符和复合赋值运算符则应该返回左侧运算对象的一个引用。

当我们在定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。有一些选择的准则如下:

赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员。
复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。
改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员。
具有对称性的运算符可能任意转换一端的运算对象,例如,相等性,关系和位运算等通常应该是普通的非成员函数
加法是对称的,因为它的两个对象都可能在左侧也可能在右侧。
如果想提供含有类对象的混合类型表达式,则运算符必须定义成非成员函数

当我们把运算符定义成成员函数时,它的左侧对象必须是运算符所属类的一个对象!

输入输出运算符必须是非成员函数,因为输入输出流对象必须是左侧运算对象,而当运算符是成员函数时,this绑定到左侧运算对象上,左侧对象必须是运算符所属类的对象。即,输入输出流对象必须是该自定义类的对象,这和输入输出流对象不是自定义类对象(是标准库中类的对象)相互矛盾。

在上述中,为了介绍重载运算符的概念,贴了一个代码,但是有些地方并不符合代码规范,或者是不符合我们平时的代码编写习惯,下面我们就一些常见的运算符加以介绍。声明为常量证明运算符一般不需要改变对象的状态,声明为引用是为了减少复制。

算术运算符定义成非成员函数(通常),计算两个对象然后存在一个局部变量内,返回局部变量的拷贝作为结果。

ope operator+ (const ope& lhs, const ope& rhs)
{
    ope sum = lhs;
    sum += rhs;
    return sum;
}

如果某个类在逻辑上有相等性的含义,则该类应该定义operator==,这样做可以使得用户更容易使用标准库算法来处理这个类,定义函数实现相同的功能会有记忆函数名和可能错误使用未定义运算符的困扰。一般定义了operator==也会定义operator!= 运算符:

...
private:
    int x_;
    std::string name_;
};

bool operator==(const ope& lhs, const ope& rhs)
{
    return (lhs.x_ == rhs.x_ && lhs.name_ == rhs.name_);
}
bool operator!=(const ope& lhs, const ope& rhs)
{
    return !(lhs == rhs);
}

对于关系运算符来说,比如operator<,并不是只要有定义了相等运算符就需要关系运算符的。假设我们同时定义了相等和小于运算符,那么当两个对象不相等时肯定会有一个对象小于另外一个对象:

A.name_ != B.name_;

如上述代码,两个对象名不相等,但是我们完全没有必要比较哪个对象名大。因此,对于自定义类来说,如果不存在逻辑可靠有必要的<定义,那这个类不定义<运算符也许会更好。

下标运算符必须是成员函数

using namespace std;
class StrVec
{
public:
    StrVec(){}
    StrVec(string& s):s_(s){}

    char& operator[](std::size_t n){
        return s_[n];
    }
    const char& operator[](std::size_t n) const{
        return s_[n];
    }
    string& getdata(){
        return s_;
    }
    const string& getdata() const{
        return s_;
    }
private:
    string s_;
};
#include
#include"operator2.h"
int main(int argc,char *argv[])
{
     string s = "hello";
    StrVec A(s);
    const StrVec B(s);

    A[0] = 'k';
    std::cout << A.getdata() << std::endl;

    //B[0] = 'k'; 会报错
    std::cout <std::endl;

    return 0;
}

为了与下标的原始定义兼容,下标运算符通常以所访问元素的引用作为返回值,这样做的好处是下标可以出现在赋值运算符的任意一端。进一步,如果一个类包含下标运算符,则它通常会定义返回普通引用的普通成员版本和返回常量引用的常量成员版本 常量版本确保在给 常量对象 取下标时不会改变对象的内容(即不能赋值)

你可能感兴趣的:(c++学习笔记)