C++ Primer第五版笔记——重载运算符(一)
重载运算符是具有特殊名字的函数:由关键字operator加上要定义的运算符号组成,与其它函数相同,也有返回值、参数列表以及函数体。
当一个运算符函数是类的成员函数时,this指针绑定到左侧运算对象。
我能只能重载已有的大多数运算符,而无权发明新的运算符号:
通常情况下也不重载逗号、取地址、逻辑与、逻辑或运算符。
输入、输出运算符:
IO标准库使用>>和<<来执行输入和输出操作,IO库定义了内置类型的版本,而类则需要自定义设和其对象的版本以支持IO操作。
输出运算符<<:
通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用:之所以是非常量的是因为向流写入内容会改变其状态;是引用是因为我们无法直接复制一个ostream的对象。
第二个形参一般是一个常量的引用,该变量是要打印的类类型:是引用的原因是希望避免复制实参;是常量的原因是不应该改变打印对象的内容。
为了与其他输出运算符一致,该函数一般返回它的ostream形参,例如:
ostream& operator<<(ostream& os,const A& a){
os<<a.i;
return os;
}
输出运算符应该是控制输出的内容,而不是输出的格式,所以不应该在函数中打印换行符。
输入运算符>>:
通常情况下,输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入到的对象的引用,返回值通常会返回某个给定流的引用。第二个形参之所以必须是非常量是因为需要改变他的状态。例如:
istream& operator>>(istream& is,A& item){
double i; //不需要初始化,因为要先读取数据后才使用它
is>>item.xxx>>i;
if(is){ //检查输入是否成功
...
}
else{ //读取操作发生错误时,输入运算符负责从错误中恢复
...
}
return is;
}
输入运算符必须处理可能失败的情况,而输出运算符不需要。
输入时的错误:
在执行输入运算符时可能发生以下错误:
1.当流含有错误类型的数据时读取操作可能失败,比如需要读取数字类型,但是输入的不是数据类型,则读取操作和后续对流的其他使用都将失败;
2.当读取操作到达文件末尾或者遇到输入流的其他错误时也会失败。
算术和关系运算符:
通常将算术运算符和关系运算符定义为非成员的函数以允许对左侧或右侧的运算对象进行转换。
相等运算符:
设计准则:
1.应当定义operator==,而非新的函数名;
2.判断参数是否重复;
3.应该具有传递性,即a==b,b==c,则a==c;
4.应有相应的operator!=;
关系运算符:
定义了相等运算符的类也常常包含关系运算符。特别是,因为关联容器和一些算法要用到小于运算符,所以定义operator<会很有用。
通常情况下,关系运算符应该:
1.定义顺序关系,令其与关联容器中对关键字的要求一致;
2.如果有定义相等运算符,则应该有一种关系表示相等
赋值运算符:
我们可以重载赋值运算符,不论形参的类型是什么,都必须将其定义为成员函数,
复合赋值运算符(比如+=)通常情况下也应该定义为类的成员,这两类运算都应该返回左侧运算对象的引用。
下标运算符:
表示容器的类通常可以通过元素的位置来访问元素,这些类通常会定义下标运算符,其必须是成员函数。
下标运算符的返回值通常是所访问的元素的引用,这样在调用赋值运算符时可以出现在任意一端,进一步,最好同时定义返回普通引用和常引用的下标运算符。
递增递减运算符:
C++语言并不要求递增/递减运算符必须是类的成员,只是建议这么做,因为它改变的是操作对象的状态。一般递增递减都有前置和后置版本,自己定义时也应该定义两个版本。
前置递增/递减运算符:
xxx operator++();
xxx operator–();
在做递增操作时,应该先有一个检查操作,判断当前的指针是否是指向最后一个元素;做递减操作时,应该先递减,然后在检查指针是否指向无效的地址。
区分前置和后置:
普通的重载形式无法区分这两种情况,它们使用的符号,函数名相同,并且运算对象数量和类型也相同。为了区分,后置版本接受一个(并不会使用)额外的int类型的参数,尽管在语法上后置函数可以使用这个参数,但是在实际情况中一般是不会使用的,它的作用就是为了和前置区分而已。
xxx operator++(int);
xxx operator–(int);
因为不会使用到int形参,所以不需为其命名,另外在后置的版本中,不需要先检查是否对象是否有效,因为后置递增递减是先返回当前状态,然后再递增或递减。
成员访问运算符:
迭代器类和智能指针类常用到解引用运算符(*)和箭头运算符(->)。重载时也需要先检查当前指针是否仍在作用范围内,如果是,解引用运算符返回当前指针所指对象的一个引用,箭头运算符返回该对象的地址。
箭头运算符必须是类的成员,解引用运算符虽然并不必须是类的成员,但通常也这么定义;另外获取元素并不会改变对象的状态,所以应该将其定义成const成员:
xxx& operator*() const{
check(); //检查
...
return x; //返回对象本身
}
xxx* operator->() const{
return &this->operator*(); //返回地址
}
对箭头运算符返回值的限定:
大多数的运算符在重载时的函数体中内容是不限制的,比如可以让递增运算符只做打印工作,但箭头运算符不同,它永远不能丢掉访问成员这个含义,重载箭头运算符时可以改变从哪个对象中取得成员,但是取得成员这件事不能变。
推荐阅读
-
详解C++编程中一元运算符的重载
-
Primer C++第五版 读书笔记(一)
-
c++通过运算符[]重载实现一重和二重数组
-
20.7.17 笔记算数运算符 复合运算符重载 比较运算重载 多态 设计原则 类的单一职责 依赖倒置 组合复用原则 里氏替换 迪米特法则 矩阵转置原理
-
c++ primer(第五版)学习笔记及习题答案代码版(第十四章)重载运算与类型转换
-
c++ primer plus第五版读书笔记
-
c++ primer(第五版)学习笔记及习题答案代码版(第三章)字符串、向量和数组
-
《C++ Primer》学习笔记_第一章 开始
-
c++ primer 第五版学习笔记-第一章 开始
-
《C++ Primer Plus》学习笔记——第五章 循环和关系表达式(一)