//错误:不能为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++里已经有了内置含义,为了避免它们的行为异于常态,所以尽量不要重载它们。
当我们在定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。有一些选择的准则如下:
赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员。
复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。
改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员。
具有对称性的运算符可能任意转换一端的运算对象,例如,相等性,关系和位运算等通常应该是普通的非成员函数
加法是对称的,因为它的两个对象都可能在左侧也可能在右侧。
如果想提供含有类对象的混合类型表达式,则运算符必须定义成非成员函数
当我们把运算符定义成成员函数时,它的左侧对象必须是运算符所属类的一个对象!
ope operator+ (const ope& lhs, const ope& rhs)
{
ope sum = lhs;
sum += rhs;
return sum;
}
...
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;
}
为了与下标的原始定义兼容,下标运算符通常以所访问元素的引用作为返回值,这样做的好处是下标可以出现在赋值运算符的任意一端。进一步,如果一个类包含下标运算符,则它通常会定义返回普通引用的普通成员版本和返回常量引用的常量成员版本 常量版本确保在给 常量对象 取下标时不会改变对象的内容(即不能赋值)
。