重载前须知

重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成。 因为重载运算符时函数, 因此它包含返回值、参数列表和函数体。
对于重载运算符是成员函数时, 它的第一个运算对象被隐式的绑定到this指针上,因此,成员函数的重载运算符的显示参数数量比运算符的运算对象少一个。

对一个运算符函数来说, 要么它是一个类的成员函数, 或者它的参数至少包含一个类类型。


某些运算符不应该被重载

对于逻辑与&&、逻辑或 || 和逗号运算符来说,重载它们会无法保留下来它们的运算对象的求值顺序。 而且对于&& 和 || 来说,它们具有的短路求值属性也无法保留。

对于取地址运算符,它又特定的内置含义,它也不该被重载。


重载运算符应该和内置类型一样的含义
  • 如果类执行IO操作,则定义移位运算符使其与内置类的IO 一致。
  • 一般定义了相等性运算符==,那么也应该定义!= 运算符。
  • 一个类定义了一个比较运算符,那么它也应该定义其他比较运算符。
  • 重载运算符的返回类型应该和内置版本的返回类型一致。

选择座位成员还是非成员函数

  • 赋值(=)、 下标([])、调用(())和成员访问箭头运算符必须定义为成员函数
  • 复合赋值运算符一般定义为成员函数,但不是必须的
  • 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,一般定义为成员函数
  • 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,通常应该为非成员函数。

输入和输出运算符

输入、输出运算符必须是非成员函数, 一般被定义为类的友元!

重载输出运算符

输出运算符的第一个形参是一个非常量的ostream对象的引用,第二个形参一般是一个常量的引用,因为输出运算符不会改变参数的值。
operator << 一般返回它的ostream形参。

Sales_data的输出运算符:

ostream& operator << (ostream& os, const Sales_data& item) {
os << item.isbn() << " " << item.units_sold << " " <<item.revenue << " " << item.avg_price();
return os; }

值得注意的是,为了和内置类型的输出运算符保持一致,我们重载的输出运算符应该尽量减少格式化操作, 尤其是换行符!


重载输入运算符

输入运算符的第一个参数应该是一个要读的流的引用,第二个参数形参应该是一个非常量的对象的引用,它返回输入流的引用。
输入运算符应该处理可能输入失败的情况!


Sales_data的输入操作:

istream& operator >> (istream& is, Sales_data& item) {

	double price;
is >> item.bookNo >> item.units_sold >> price;
if (is) // 检测输入流
item.revenue = item.units_sold * price;
else
item = Sales_data(); // 输入失败,对象被赋予默认状态 return is;
}

正如程序中看到的,重载的输入运算符应该要处理可能输入失败的情况,当读取失败时,输入运算符应该负责从错误中恢复。


算术和关系运算符

通常情况下,我们将算术和关系运算符定义为非成员函数,以允许向左侧或右侧的运算对象进行转换。

算术运算符

一般的,如果定义了算术运算符,则它一般也需要定义一个对应的复合赋值运算符。

Sales_data operator + (const Sales_data& lhs, const Sales_data& rhs) {

	Sales_data sum = lhs;
sum += rhs;
return sum;
}

同时定义了算术运算符和相应的复合赋值运算符,则一般用复合赋值运算符实现算术运算符。



相等运算符

判断两个类是否相等时,我们应该比较它的所有成员:
bool operator == (const Sales_data& lhs, const Sales_data& rhs) {

	return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
} bool operator != (const Sales_data& lhs, const Sales_data& rhs) { return !(lhs == rhs);
}

如果定义了==, 则运算符应该判断给定的两个对象是否含有重复数据。
==应该具有传递性,如果 a == b, b == c, 则 a == c。
如果定义了==,则我们也应该定义 !=
相等运算符和不等运算符中的一个应该把工作委托给另一个。


关系运算符

一般定义了相等运算符的类,也应该定义关系运算符,特别的是,关联容器和一些算法需要用到小于运算符,因此定义operator < 比较有用。
关系运算符应该:1. 定义顺序关系,令其与关联容器对关键字的要求一样,严格弱序。 2. 如果类同时有 == 运算符,则定义一种关系令其与==保持一致,如果两个对象是!= 的,那么一个对象应该 < 另一个。

存在唯一的逻辑可靠的 < 定义,才考虑为一个类定义 < 运算符。如果类同时定义了==, 当且仅当<的定义与==产生的结果一致时才定义<运算符。


赋值运算符

赋值运算符必须定义为成员函数。
重载赋值运算符应该与内置类型的赋值运算符保持一致,应该返回左则运算对象的引用:

StrVec& StrVec::operator = (initializer_list<string> il) {

	auto data = alloc_n_copy(il.begin(), il.end());
free();
first = data.first;
last_end = cap = data.last_end;
return *this;
}

赋值运算符应该先释放左则对象的内存空间。


复合赋值运算符

复合赋值运算符不一定是类的成员函数,不过我们应该把包括复合赋值在内的所有赋值运算都定义在类的内部。复合赋值运算符也要返回左侧运算对象的引用:
Sales_data& Sales_data::operator += (const Sales_data& rhs) {

	units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}


下标运算符

下表运算符必须是成员函数。

下标运算符应该以所访问的元素的引用作为返回值,而且我们最好定义常量版本和非常量版本。、

class StrVec {
public:
std::string& operator [] (size_t n) { return first[n]; }
const std::string& operator [] (size_t n) const { return first[n]; }
private:
std::string* first; // 指向数组的首元素
};

递增和递减运算符

递增和递减运算符通常应该定义为类的成员函数。

前置版本:

class StrBlobPtr {
public:
StrBlobPtr& operator++();
StrBlobPtr& operator--();
//
};

前置版本的递增/递减运算符应该返回递增或递减后的对象的引用。



后置版本:
class StrBlobPtr {
public:
StrBlobPtr operator++(int);
StrBlobPtr operator--(int);
//
};

为了前置版本和后置版本,将后置版本中的参数列表中添加一个int型参数,但是这个int型只是用来区分前置和后置版本的运算符,并不使用它。

后置版本的递增/递减运算符应该返回递增/递减前的对象的值。


成员访问运算符

箭头运算符必须是类的成员, 解引用也应该是类的成员,尽管并非如此。


class StrBlobPtr {
public:
std::string& operator*() const {
auto p = check(curr, "dreference past end");
return (*p)[curr];
} std::string* operator->()const {
return & this->operator*();
}
//
};

对箭头运算符返回值的限定

箭头运算符永远不能丢掉获取成员这一事实。

对于形如point->mem的表达式,point必须是指向类对象的指针或者是一个重载了operator->的类的对象,根据point类型不同,可分为两种情况:
(*point).mem; // (1)
point.operator()->mem; // (2)

如果point是指针,则应该使用内置的箭头运算符,表达式等价于上面的第一条。

如果point是定义了operator->的类的一个对象,则使用operator->的结果来获取mem。如果该结果是一个指针,则执行第一步,如果该结果本身含有重载的operator->(), 则重复调用当前步骤。


重载的箭头运算符必须返回类的指针或自定义了箭头运算符的某个类的对象。



C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符的更多相关文章

  1. C++ Primer : : 第十四章 : 重载运算符与类型转换之类型转换运算符和重载匹配

    类型转换运算符 class SmallInt { public: SmallInt(int i = 0) : val(i) { if (i < 0 || i > 255) throw st ...

  2. C++Primer 第十四章

    //1.当运算符作用于类类型运算对象时,可以通过运算符重载重新定义该运算符的含义.明智的使用运算符重载能令程序更加易于编写和阅读. //2.重载的运算符是具有特殊名字的函数,它们由关键字operato ...

  3. 【C++】《C++ Primer 》第十四章

    第十四章 重载运算与类型转换 一.基本概念 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成.也包含返回类型.参数列表以及函数体. 当一个重载的运算符是成员函数时 ...

  4. C++ Primer Plus学习:第十四章

    第十四章 C++中的代码重用 包含对象成员的类 将类的对象作为新类的成员.称为has-a关系.使用公有继承的时候,类可以继承接口,可能还有实现(纯虚函数不提供实现,只提供接口).使用包含时,可以获得实 ...

  5. 《Linux命令行与shell脚本编程大全》 第十四章 学习笔记

    第十四章:呈现数据 理解输入与输出 标准文件描述符 文件描述符 缩写 描述 0 STDIN 标准输入 1 STDOUT 标准输出 2 STDERR 标准错误 1.STDIN 代表标准输入.对于终端界面 ...

  6. perl 第十四章 Perl5的包和模块

    第十四章 Perl5的包和模块 by flamephoenix 一.require函数  1.require函数和子程序库  2.用require指定Perl版本二.包  1.包的定义  2.在包间切 ...

  7. 第十四章——循环神经网络(Recurrent Neural Networks)(第一部分)

    由于本章过长,分为两个部分,这是第一部分. 这几年提到RNN,一般指Recurrent Neural Networks,至于翻译成循环神经网络还是递归神经网络都可以.wiki上面把Recurrent ...

  8. 第十四章——循环神经网络(Recurrent Neural Networks)(第二部分)

    本章共两部分,这是第二部分: 第十四章--循环神经网络(Recurrent Neural Networks)(第一部分) 第十四章--循环神经网络(Recurrent Neural Networks) ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段 代码工程地址: https://github. ...

随机推荐

  1. sublime_text3配置

    推荐视频 http://v.youku.com/v_show/id_XMzU5NzQ5ODgw.html 介绍的是2版本 准备工作 点击连接下载Sublime Text3 初始化用户信息 第一次安装该 ...

  2. 和Java相关的一些好文章(不定期更新)

    1.Java 集合类详解 (包括arraylist,linkedlist,vector,stack,hashmap,hashtable,treemap,collection等). 2.Java 理论与 ...

  3. Android:padding和android:layout_margin的区别

    padding是站在父view的角度描述问题,它规定它里面的内容必须与这个父view边界的距离. margin则是站在自己的角度描述问题,规定自己和其他(上下左右)的view之间的距离

  4. window.onload()和$(function(){});的区别

    1.window.onload必须等到页面中所有元素加载完之后才会执行(包括图片.视频等)而$(function(){});是在结构绘制完毕之后执行,二者的执行时机是不同的,一般来说后者会首先执行 2 ...

  5. laravel Ajax post方式的使用

    以jquery ajax 的post的方式为例 验证邮箱输入格式是否正确 html <div class="fl"> <input type="emai ...

  6. C学习笔记(八)字符输入输出和输入确认

    缓冲区 缓冲区分为两类:完全缓冲(fully buffered)I/O和行缓冲(line-buffered)I/O.完全缓冲在缓冲区满时被清空(内容被发送至目的地).这种类型常出现在文件输入中.缓冲区 ...

  7. jQuery1.9为动态添加元素绑定事件以及获取和操作checkbox的选择属性

    1.jQuery为动态添加的元素绑定事件:在1.7之后,添加了live()方法,1.9后又被移除,1.9中可用on()方法: $(function() { $('.btn').on('click',  ...

  8. c#调用c++动态链接库的问题

    1. Server Error in '/' Application. Object reference not set to an instance of an object. 这个问题是接口中的方 ...

  9. row_number() OVER(PARTITION BY)函数介绍

      OVER(PARTITION BY)函数介绍 开窗函数               Oracle从8.1.6开始提供分析函数,分析函数用于计算基于组的某种聚合值,它和聚合函数的不同之处是:对于每个 ...

  10. CodeForces #369 div2 D Directed Roads DFS

    题目链接:D Directed Roads 题意:给出n个点和n条边,n条边一定都是从1~n点出发的有向边.这个图被认为是有环的,现在问你有多少个边的set,满足对这个set里的所有边恰好反转一次(方 ...