当前课程知识点:基于Linux的C++ > 第十讲 操作符重载 > 10.2 四则运算符重载(一) > LinuxCPP1002
我们首先来看四则运算符的重载
我要同学们设计一个类
这样的一个类是一个数偶类
我们为这样一个数偶类
来定义专用的四则运算
数偶是什么东西呢
数偶是一个包含两个元素的
特殊的数据结构
这样的两个数据
我们往往是以成对的方式来操纵和访问
这样的东西我们称它为数偶
在C++的标准模板库里边
事实上替我们提供了一个类似的概念
它称之为pair
我们这里边使用Couple
来定义这样的一个数偶
Couple这个类定义的时候
我们按照上一讲的内容
实现它的基本的构造函数
我们要为它实现四则运算
四则运算肯定不是一个两个
那是加减乘除余 总共五个
所以我们这里边只以加法和乘法为例子
只提供这样两个运算 给同学们演示
我们怎么重载它的操作符
当重载加法和乘法
这两个数偶运算的操作符的时候
我们必须按照这样的一个格式
来定义成员函数的原型
operator+ 这就是这个
加法操作符的函数的名字
加号前面要有一个operator
表示它是个操作符
operator+就是这个函数的名字 记住了
括号里边要提供这个操作符的参数
对这样一个加法操作来讲
它实际上是带了两个成员
其中一个成员 当然是这个对象本身
当你在这个对象上
应用它的成员函数的时候
那么实际上
它的第一个参数就是这个对象本身
我们上一讲也说了
它实际上是通过this指针传进来的 对吧
那么第二个操作数
就是你在这个成员函数的
参数列表里提供的那个参数
所以对一个加法操作也好
乘法操作也好 它带的双操作数
左操作数就是this指针所指向的那个对象
而右操作数就是我们提供的这个参数
参数中我们使用一个对const Couple的引用
你必须按照这样一个方式来
如果你不按照这个方式来
那么它就可能
和我们的四则运算的语义不吻合
为了要和我们数学上的四则运算相吻合
我们必须提供引用
这是四则运算符重载的函数原型
你看到这两个成员函数operator+、operator*
的格式实际上是一样的
现在我们来实现这段代码
我们提供的就这两个函数
这两个函数实现其实都非常简单
就简短的两条语句
对于operator+
我们要构造一个Couple类的对象_t
本对象的_a成员和c对象的_a成员
本对象的_b成员和c对象的_b成员
分别累加 我们构造一个新的Couple
然后我们返回这个Couple
这个就是我们operator+重载的全部的语义
我们要做的事就这么多
operator* 实际上它的实现和刚才那个一样
只不过是两个字段分别地相乘
也构造这样的一个对象把它返回就完了
实现是很简单的
我们来看怎么调用这两个成员函数
我们构造Couplea和Coupleb
一个是“1,2” 一个是“3,4”
然后我们又定义了两个对象c和d
c = a + b,d = a + b + c
a+b 这是一个标准的加法运算
就像我们数学上的加法运算一样
但是在C++的内部实现中
加法操作并没有应用到
我们自己定义的Couple类上
我们要想让加法
能够在我们的Couple类上工作
那么我们就必须在Couple类上重载operator+
这是一个强烈的要求
你不实现 这个加法就不能做
你说我提供一个成员函数名字叫Add
行不行 行
那是可以的 没问题
它事实上可以工作的
但是我们有了重载的加法操作
我就可以让Couple这两个对象
看上去就像普通的数学运算一样
就用数学的加法就把它加在一起了
这多方便哪
对于这个对象的使用者
对于这个类的使用者来讲
你这个类库的架构
别人用起来就非常方便
重载操作符的目的其实就在这里
当我们产生一次c=a+b这个动作的时候
那么C++的编译器就会调用我们的operator+
它怎么调用呢
它把这个加法的左操作数
作为我们的第一个对象
把右操作数作为它的第二个对象
左操作数是谁呢 *this 就是它
我们这个this指针指向的那个对象
就是我们的左操作数
所以它实际上就指代我们的左操作数
所以这个加法将是在a
这个对象上面调用的成员函数
同学们特别注意这一点
所以它实际上就等价于c = a.operator+()
括号里传的参数是b这个对象
当然接受实际上是那个对象的一个常引用
所以等价于c = a.operator+(b)
你看到的
我们就是按照这样的一个方式来调用的
做完以后我们得到结果
c最终会变成4和6
下面那个例子:d = a + b + c
这里面有两个加法
所以这就意味着实际上要调用两次
我们的成员函数operator+
第一次是在a上面调用
第二次是在a+b的产生出来的
那个Couple类的结果上调用
注意这一点
所以它实际上等价于d=a.operator+(b)
做完了以后
它会返回数偶类的一个对象
所以我们在这个数偶类对象上边
调用:“.operator+(c)”
然后才能够算出结果来
乘法也一样 c=a*b
它实际就等价于c=a.operator*(b)
这个就是四则运算符的重载的方式
那么这里面就会有几个问题
一 假设我们需要一个
数偶和一个标量的乘积
比如讲我们要有一个倍乘
我想一个数偶乘上一个整数k
这个乘法我就是
把这个整数k乘到两个成员上边
为什么要一个标量乘呢
因为我要模拟两个同样的数偶的加法
这不相当于乘2吗 对吧
我认为我们要实现一个标量乘法
对于这个数偶来说实现这样一个标量乘法
那么我们怎么做呢
我们刚才那个operator*肯定
完成不了这个工作
那是两个数偶的相乘
所以我们现在要把一个数偶
和一个整数乘起来
所以我们要实现像这样的
一个特殊的乘法:operator*
这是它的成员函数
它后面带的那个参数是个const int & k
返回值仍然是Couple 这是个标量乘
带着那个参数
就不是对于const Couple的一个引用
而是对const int的一个引用
传过来是一个整数
你实际上单传一个整数其实也可以
你看它的实现 operator*
我们就把这个k
分别乘到它的两个成员上
然后我们构造这样一个Couple对象_t
返回它就完了
实现上一样很简单 看我们怎么用
a+b 还是刚才的
a+b+c 跟刚才也一样
我们现在看的是最后两个:c=c*k,d=d*2
第一个我们在Couple对象c上边调用operator*
去做那个标量乘法
而不是一个数偶乘法
这个等价于——它事实上等价于——c=c.operator*(k)
它就会把k这个值3
直接乘到了数偶的两个成员上
d=d*2 它就把2乘到了这个数偶上
性质上是一样的
我们有第二个问题
你这个参数必须是
Couple类对象的一个引用吗
我们实现这个四则操作符的时候
按照它的成员函数的模式去实现的嘛
所以我们这里面就涉及到几个主要的问题
对吧 这是第二个问题
你的参数必须是个
Couple类对象的一个常引用吗
不 你可以不使用引用
语法上是可以的 但是你要记得
如果你不使用引用 就会产生一个值传递
我们的函数参数传递
在C++语言里面实际上就两种参数传递方式
一个是值传递 一个是引用传递
你如果不是使用引用的
那么它就不是引用传递
它就是值传递 一个值传递
它就会产生一个值的拷贝动作 对吧
它要把实际参数的值拷贝给形式参数嘛
这个值的拷贝动作
当数据对象很小的时候没关系
你比如说它是个整数 它无所谓
如果是个大对象 这个拷贝
就会极大地消耗CPU的时间
降低我们程序的效率
所以如果你不使用引用
你会产生对象拷贝的这样一个额外动作
会降低效率
第二个
这个引用也不见得一定必须是个const的
你比如constCouple&
这个constCouple&引用是OK的
直接就是Couple& OK的
没有问题 并不一定必须是一个const
它所引用的目标对象必须是个const
没有这样的一个要求
可以不是const 但是你要记得
如果不是const
如果你实现的时候
那个引用不是对一个常对象的引用
而是对一个普通对象的引用
你就要记得 在这个函数体内部就有可能
通过那个引用修改那个目标对象的值
非常引用是有可能修改目标对象的值的
这是一个非常重要的设定
所以你要注意
如果你这个成员函数里边
并不需要修改目标对象的值
这个引用就必须设定成const Couple
而不能设定成Couple&
一定是const Couple& 注意这个
第三个
那么我能不能够使用指针作为函数参数呢
其实也行
但是你要记得
如果你使用指针作为操作符重载的参数
那么它的使用方式
就和我们的数学习惯是不吻合的
因为你的实际参数必须传
那个目标数据对象的地址
而不是传那个目标数据对象的名字本身
不能写那个目标数据对象的名字本身
你必须写那个目标数据对象的地址
它和数学上用法 它是不吻合的
所以特别注意
真实的操作符重载我们从不传递指针
它和数学语义不吻合
这在重载操作符中是大忌
我们后面会详细地讨论
问题三 返回值
它的类型必须是Couple类的对象吗
返回一个Couple类的对象的一个引用行不行
一 你可以返回引用
但是要特别注意
这个引用所引用的那个目标数据对象
要么是一个全局对象
要么就是通过参数传递进来的某个对象
就是在函数内部可以访问的
并且在这个函数结束之后
仍然存在的一个对象
你不能够返回这个函数内部的
局部量的一个引用
那是非常可怕的一件事情
当这个函数结束以后
这个引用实际上不存在了
那个对象都已经不存在了嘛
引用还上哪存在去
所以它会导致未知的结果
你可以传回引用
返回值可以是一个引用类型
但是要特别记得
能够返回什么对象的引用 它是受限的
所以我们不建议在操作符重载过程中
使用引用类型作为函数的返回值
注意这个 对于我们的四则操作符
不建议在重载四则运算操作符的时候
使用引用作为函数的返回值
还有 需要将右操作数累加到左操作数上
并且返回左操作数的时候
你就应该使用引用作为函数的返回值了
你比如说 我们后面会看到的
什么加赋、减赋、乘赋、除赋、余赋
像这样类似的操作
因为它涉及到把一个对象
累加到这个对象上
并且返回那个累加后的对象
所以实际上我们应该返回那个
累加后的对象的一个引用
而这个累加后的对象
实际上就是我们this指针所指向的那个对象
它在我们这个成员函数结束以后
它仍然存在 所以我们返回它的引用
一点问题都没有
只有这样才能够保持C++里边
关于加赋、减赋、乘赋、除赋、余赋
操作符的语义
这个时候返回引用是恰当的
而对于我们价加减乘除普通四则运算
返回引用
实际上是不恰当的
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口