大家好
欢迎回来继续学习
C++语言程序设计
这一节我们来学习
如何将运算符重载为
类外的非成员函数
我们知道
当我们要将运算符
重载为类的成员函数的时候
它的左操作数
必须是这个类的对象
如果我们面临的运算
它的左操作数不是类的对象
比如说我们要实现
一个实数加复数
实数在加号左边
那么我们还能够通过
重载成员函数
解决这个问题吗
那就不行了
这个时候我们就需要将
这个运算符重载为
类外的一个普通的全局函数
还有一种情况
虽然说一个运算符的左操作数
是类的对象
但是这个类
不是由我们自己定义的
比如说
是类库里面现成的类的对象
那么这个类也不由我们自己设计
我们也没有权利去给这个类
增加一个什么重载运算符函数
那么这个时候
如果我们要想能够让这个对象
跟我们自定义的
另外一个类的对象
去运算的话
我们就只能将运算符重载为
类外的非成员函数
接下来我们看看
具体的语法是什么样子
运算符要重载为
非成员函数的时候呢
它的形参就要列出
所有的操作数
比如说二元运算符
它的形参
就应该是两个操作数
两个形参
那么次序呢
是自左至右排列的各个操作数
那么重载为非成员函数的时候
后置的++和--
除了要一个
它是一个单步运算符
除了它要有一个形参
作为参与运算的这个对象以外
还要增加一个整数
以区分前置和后置的单步运算符
那么还有一点非常重要
就是当我们将运算符
重载为非成员函数的时候
参数中要至少有一个
是自定义类型的对象
不能两个参数
都是基本数据类型的
我们不能说
重载一个函数
然后把整数的加法含义
给修改一下
那这是绝对不可以的
所以只能是为自定义类型
去扩展
去重新定义这个已有的运算符
而不能对原有的数据类型
把它的运算的含义去修改了
这是不允许的
那么在运算符重载函数中呢
可能会需要
操作某个类对象的私有成员
这个时候通常的做法呢
就是将函数
声明为这个类的友元
当然
你不声明为友元也是可以的
我们可以通过类的公有接口
去访问私有数据成员
但是呢
去声明为友元以后呢
运算的效率就会比较高一些
如果我们希望
将一个双目运算符B
重载为类外的非成员函数
那么这样重载完以后
如果我们在程序中
写了这样的表达式
操作数1 B 操作数2
那么这个时候就相当于
调用了这样一个全局函数
oprdB
括号里的两个形参呢
分别是操作数1和操作数2
如果是重载前置的单目运算符B
那么重载完了以后
我们去用这个单目运算符
对一个对象oprd
进行操作的时候
就相当于调用oprdB这个函数
然后括号里有一个实参
就是参与运算的这个对象
而后置的单目运算符++--呢
它的参数
要比前置的多一个整数
调用的时候呢
第二个参数默认的是给它一个0
而且这个0
在函数体中
是不许去使用的
下面这个例子呢
就以C成员函数的形式
为复数类重载加法 减法运算
并且还为它重载了一个
插入运算符
这样的话
我们就可以用cout
整个输出一个复数对象了
重载插入运算符
就是只能重载为类外的成员函数
为什么呢
因为它的左操作数
虽然是一个类的对象
但是它是系统里面预定义好的
这个类库里面的一个
输出流类的对象cout
不是我们程序员自定义的对象
那这个时候
我们就无法在类库里面
这个输出流类里面
去加一个重载运算符函数
这个没有权限去加的
所以我们就把它重载为
类外的非成员函数
实现对于复数对象的
整体输出的功能
那么这个题目的具体要求呢
就是将+ -
这两个双目运算符
重载为非成员函数
并且呢
把它声明为复数类的友元
这样的话
它的运算效率会比较高一些
这样它的两个操作数呢
都是复数类的常引用
当然我们也可以
将复数类对象作为参数
但是传引用
比传对象的效率要高一些
但是传引用呢
就有可能实现双向传递了
那么在运算符进行运算的时候
如果出错
改变了操作数的值
那就不是我们希望的结果了
所以这里呢传引用
但是加一个const来修饰
传常引用
既提高了参数传递的效率
又保证了实参的安全性
然后将双目运算符
这个插入运算符
也重载为非成员函数
也将它实现为复数类的友元
它的左操作数呢
是这个类库里头
标准库里面这个ostream
这个类的对象引用
我们在验证它的时候呢
会传给它cout
这个预定义好的对象
至于ostream这个类是个什么
我们在十一章会给大家介绍
io流类库的时候
大家就清楚了
那么右操作数呢
是复数类对象的常引用
这个操作符的函数
它要求返回
必须是一个ostream的引用
那在这个重载了以后呢
我们要求它能够支持
这种级联的输出
cout它用插入运算符
输出一个对象a
再用插入运算符输出一个对象b
那么实际上它是
两次调用了这个operator
插入运算符函数
第一次调用 先输出a
输出完了a以后
这个函数本身的返回值
就是一个ostream的对象引用
所以又把它作为
第二次调用的第一个参数
这样就实现了
这样的级联的输出
接下来呢
我们来看一下
这个例题的源代码
以及它的运行效果
这一次呢
我们将这个加法和减法
还有插入运算符函数
都重载为类外的全局函数
我们看一下
这几个函数的实现
它不再是
Complex类的成员函数了
它就是普通类外的独立的函数
所以说呢
要做加法
那么两个操作数都得作为参数
传过来
减法也是一样
操作数是两个
那么插入运算符呢
它的第一个操作数
是ostream的对象
是一个输出流类的对象
因为你要指定
往哪个输出流里面插入
对吧
到目前为止呢
我们所用过的ostream对象呢
就是这个cout
向显示器输出的这个输出流
第二个参数呢
就是我们要输出的这个复数对象
由于我们需要在函数体中
去访问复数对象的数据成员
为了提高访问效率呢
我们希望能够直接通过
对象名.成员名的方式访问
所以在这个复数类中
就把这三个运算符重载函数
都声明为友元了
那么这个加法减法函数的实现
跟前面的成员函数的实现
都是类似的
就是两个操作数的实部
虚部分别相加
两个操作数的实部
虚部分别相减
然后构造临时无名对象 返回
那么现在看
这个插入运算符函数
刚才解释的这两个参数
还有呢
这个函数的返回值
要求必须是一个ostream
对象的引用
通常就是应该将第一个操作数
返回
所以我们就将这第一个流对象
作为返回值返回
返回以后
它就结果还是一个输出流对象
所以呢
它能够去实现级联的输出
现在我们看
在主函数中验证一下
仍然是定义两个对象c1 c2
然后我们在这两个对象上面
作减法 作加法
这个效果跟前面
我们把它定义成成员函数
效果是一样的
那接下来呢
我们再用cout
用插入运算符去输出对象c3
这一次呢
我们不用写什么display函数
或者显示其他的显示函数了
对象可以整体输出了
那么这个左操作数是cout
实际上这是一个级联输出
级联输出了一个字符串
然后这第一次输出
输出字符串以后
它的返回值仍然是cout
所以在输出c3的时候
这个插入运算符
它的左侧的操作数
还是cout对象
然后把c3输出
输出完了以后
我们自己定义的
这个插入运算符函数
也是遵循的这种规则
它返回的也是第一个参数
所以它返回的还是cout对象
那么再用cout去输出这个换行