1.什么情况下重载的运算符与内置运算符有什么异同?
区别:
①重载操作符必须至少有一个class类或枚举类型;
②重载操作符不保证操作数的求值顺序(逗号,取地址,逻辑与和逻辑或(,&,&&,||)),
例如:对&&与||的重载版本不再支持“短路求值”的特性,两个操作数都要进行求值,
而且不规定操作数的求值顺序;
相同:
对于优先级、结合性、操作数的数目都是不变的;
2.如何显示调用运算符?
//对于类成员函数
string s1("s1"), s2("s2");
s1.operator+=s2; //相当s1+=s2;
//对于友元函数
operator<<(cout, s1); //相当于cout<
3.各个运算符的声明和注意事项
3.1重载输出运算符<<
①一般定义为类的友元函数,格式如下
ostream& operator<<(ostream&, const T&);
②输出运算符应该主要负责打印对象内容而非控制格式,一般不打印换行符;
3.2重载输入运算符>>
①一般定义为类的友元函数,格式如下
istream& operator>>(istream&, T&);
②输入运算符必须处理输入可能失败的情况;
istream& operator>>(istream& is, T& t)
{
is >> t.a >> t.b;
if(is) //判断是否发生failbit(内容错误), eofbit(文件耗尽), badbit(流错误)
{
//判断输入合法值
...
}
else
t = T(); //恢复t
return is;
}
3.3赋值算术运算符(+=,-=,*=,/=...)
①涉及到对类成员修改,返回值类型为引用,输入参数一般为const&,由于和类直接相关
应类成员
T& operator+=(const T&);
3.4算术运算符(+,-,*,/...)
①通常定义为非成员函数,一般不需要改变运算对象的状态,所以形参都是常量引用,返回
一个新值。
T operator+(const T&, const T&);
②如果类同时定义了算术运算和对应的复合赋值运算符,则通常应该用复合赋值实现算法运算
T operator+(const T& lhs, const T& rhs)
{
T tmp = lhs; //拷贝lhs
tmp += rhs; //将rhs加入tmp中
return tmp;
}
3.5相等/不相等运算符(==,!=)
①一个运算符应该依赖另一个运算符进行工作
bool operator==(const T&, const T);
bool operator!=(const T& lhs, const T& rhs){
return !(lhs == rhs);
}
②如果类在逻辑上有相等含义,那应该定义operator==,这样做可以方便调用标准库算法。
3.6关系运算符
①关联容器和一些算法要用到<运算符,所以定义operator<比较有用。格式如下:
bool operator<(const T&, const T);
②定义operator<运算符,保证逻辑正确,即:
1)若a < b , 则 b < a 为 false
2)若a < b, b < c,则 a < c
3)若a != b, 则(a < b)|| (b < a)为 true
3.7赋值运算符(=)
①主要用在拷贝赋值和移动赋值,无论什么类型赋值运算符必须定义为成为函数
T& operator=(const T&); //拷贝赋值
T& operator=(T&&); //移动赋值
②对于含容器的类,可以采用initializer_list进行初始化
T& operator=(initializer_list il);
3.8下标运算符([])
①必须是成员函数的版本。如果一个类包含下标运算符,则它通常会定义两个版本:
一个返回普通引用,另一个是类的常量成员并且返回常量引用。
ST& operator[](std::size_t n);
const ST& operator[](std::size_t n) const;
②在返回值为引用且输入参数会出现不合法的情况时,可以写个check的私有方法,
当输入参数不合法时,抛出异常。
inline void T::check(std::size_t i, const std::string &msg) const
{
if(i >= ret->size())
throw std::out_of_range(msg);
}
3.8递增运算符(++,--)
①一般定义为类的成员,定义递增和递减运算符的类应该同时定义前置和后置版本。
ST& operator++(); //前置版本
ST& operator--();
ST operator++(int); //后置版本
ST operator--(int);
3.9成员访问运算符(->,*)
①箭头运算符必须为类的成员。解引用运算符通常也是类的成员,尽管并非必须如此。
T& operator*() const {} //一般为const this
T* operator->()const //一般可以通过调用解引用实现
{ return &this->operator*();}
②
重载的箭头运算符必须返回类的指针或自定义了箭头运算符的某个类的对象
3.10函数调用运算符
①函数调用符必须是成员函数。一个类可以定义多个不同版本的调用运算。相互之间因该在
参数数量和类型上有所区别;
class PrintString{
public:
PrintString(ostream& o = cout, char c = ' '):os(o), seq(c) {}
void operator()(const string &s) const {os << s << seq; }
private:
ostream& os;
char seq;
};
//创建了一个类型为PrintString的函数对象
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
②lambda表达式是函数对象。
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a){return a.size() >= sz;})
该lambda表达式产生如下临时类型
class SizeComp{
public:
SizeComp(size_t n):sz(n) {}
bool operator()(const string& s) const
{ return s.size() >= sz;}
private:
size_t sz;
}
③标准库定义的函数对象如下:
算术类:plus,minus,multiplies,divides,modulus,negate
关系类:equal_to,not_equal_to,greater,greater_equal,less,less_equal
逻辑类:logical_and,logical_or,logical_not
3.10类型转换运算符
①类型转换运算符必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。
类型转换函数通常应该是const;
operator type() const; //如 operator int() const;
②为了避免错误的操作,可以将类型转化符定义成explicit,进行显示调用;
4.总结
1.赋值(=),下标([]),调用(())和成员访问箭头(->)运算符必须是成员。
2.复合赋值运算符一般来说应该是成员,但非必需;
3.改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用
运算符通常应该是成员;
4.具有对称性的运算法可能转换任意一端的运算对象。例如算法,相等,关系他们通常应该
是非成员函数。