当前课程知识点:C++语言程序设计进阶 > 第八章 多态性 > 运算符重载 > 运算符重载为非成员函数
有些运算符不能重载为成员函数,例如二元运算符的左操作数不是对象,或者是不能由我们重载运算符的对象
函数的形参代表依自左至右次序排列的各操作数。
重载为非成员函数时
参数个数=原操作数个数(后置++、--除外)
至少应该有一个自定义类型的参数。
后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
双目运算符 B重载后,
表达式oprd1 B oprd2
等同于operator B(oprd1,oprd2 )
前置单目运算符 B重载后,
表达式 B oprd
等同于operator B(oprd )
后置单目运算符 ++和--重载后,
表达式 oprd B
等同于operator B(oprd,0 )
• 将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。 • 将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用,用以支持下面形式的输出:
cout << a << b;
该输出调用的是:
operator << (operator << (cout, a), b);
源代码:
//8_3.cpp #include <iostream> using namespace std; class Complex { public: Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { } friend Complex operator+(const Complex &c1, const Complex &c2); friend Complex operator-(const Complex &c1, const Complex &c2); friend ostream & operator<<(ostream &out, const Complex &c); private: double real; //复数实部 double imag; //复数虚部 }; Complex operator+(const Complex &c1, const Complex &c2){ return Complex(c1.real+c2.real, c1.imag+c2.imag); } Complex operator-(const Complex &c1, const Complex &c2){ return Complex(c1.real-c2.real, c1.imag-c2.imag); } ostream & operator<<(ostream &out, const Complex &c){ out << "(" << c.real << ", " << c.imag << ")"; return out; } int main() { Complex c1(5, 4), c2(2, 10), c3; cout << "c1 = " << c1 << endl; cout << "c2 = " << c2 << endl; c3 = c1 - c2; //使用重载运算符完成复数减法 cout << "c3 = c1 - c2 = " << c3 << endl; c3 = c1 + c2; //使用重载运算符完成复数加法 cout << "c3 = c1 + c2 = " << c3 << endl; return 0; }
大家好
欢迎回来继续学习
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去输出这个换行
-导学
--导学
-继承的基本概念和语法
-第七章 继承与派生--继承的基本概念和语法习题
-继承方式
-第七章 继承与派生--继承方式
-基类与派生类类型转换
-第七章 继承与派生--基类与派生类类型转换
-派生类的构造和析构
--派生类的构造函数
--派生类的析构函数
--第七章 继承与派生--派生类的构造和析构
-派生类成员的标识与访问
--虚基类
-第七章 继承与派生--派生类成员的标识与访问
-小结
--小结
-综合实例
--第七章综合实例
-实验七
--实验七
-导学
--导学
-第八章 多态性--导学
-运算符重载
--运算符重载的规则
-第八章 多态性--运算符重载
-虚函数
--虚函数
--虚析构函数
--虚表与动态绑定
-第八章 多态性--虚函数
-抽象类
--抽象类
--第八章 多态性--抽象类
-override与final
-第八章 多态性--override与final
-小结
--第八章小结
-综合实例
--第八章综合实例
-实验八
--实验八
- 第八章讲义
-导学
--导学
-模板
--函数模板
--类模板
-第九章 模板与群体数据--模板
-线性群体
--线性群体的概念
-第九章 模板与群体数据--线性群体
-数组
--数组类模板
-链表
--链表类模板
-第九章 模板与群体数据--链表
-栈
--栈类模板
--栈类模板课后习题
--例9-9 栈的应用课后习题
-队列
--队列类模板
-第九章 模板与群体数据--队列
-排序
--排序概述
--插入排序
--选择排序
--交换排序
-第九章 模板与群体数据--排序
-查找
--查找
--查找课后习题
-小结
--小结
-综合实例
--综合实例
-实验九
--实验九
- 第九章讲义
-导学
--导学
-泛型程序设计及STL的结构
--STL简介
-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构
-迭代器
--迭代器
-第十章 泛型程序设计与C++标准模板库--迭代器
-容器的基本功能与分类
-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类
-顺序容器
--顺序容器的特征
--第十章 泛型程序设计与C++标准模板库--顺序容器
-关联容器
--集合
--映射
-第十章 泛型程序设计与C++标准模板库--关联容器
-函数对象
--函数对象
--函数适配器
-算法
--算法
-小结
--第十章小结
-综合实例
--综合实例
-实验十
--实验十
- 第十章讲义
-导学
--导学
-I/O流的概念及流类库结构
-第十一章 流类库与输入/输出--I/O流的概念及流类库结构
-输出流
--输出流概述
--向文本文件输出
--向二进制文件输出
--向字符串输出
-第十一章 流类库与输入/输出--输出流
-输入流
--输入流概述
--输入流应用举例
--从字符串输入
-第十一章 流类库与输入/输出--输入流
-输入/输出流
--输入/输出流
-第十一章 流类库与输入/输出--输入/输出流
-小结
--小结
-综合实例
--综合实例
-实验十一
--实验十一
- 第十一章讲义
-导学
--第12章导学
-异常处理的思想与程序实现
-第十二章 异常处理--异常处理的思想与程序实现
-异常处理中的构造与析构
-第十二章 异常处理--异常处理中的构造与析构
-标准程序库异常处理
-第十二章 异常处理--标准程序库异常处理
-小结
--第12章小结
-综合实例
--综合实例
-实验十二
--实验十二
- 第十二章讲义