当前课程知识点:基于Linux的C++ > 第十讲 操作符重载 > 10.5 赋值操作符重载(一) > LinuxCPP1005
接下来的一节
我们讨论赋值操作符的重载
赋值操作符重载比较复杂
涉及到几个地方
一个是赋值操作符重载的
一般形式是什么样的
第二个就是复合赋值操作符的重载
比如加赋、减赋、乘赋、除赋、余赋
这几种赋值操作符的重载
我们应该怎么去处理
然后是非常重要的一节
就是赋值构造和拷贝构造
我们上一讲讨论类与对象的时候
已经谈到过如何进行拷贝构造
但我们这里的一讲 将特别讨论
赋值构造和拷贝构造的具体实现
包括在构造一个对象的时候
我们是使用深拷贝机制
还是使用浅拷贝机制
最后一节 我们讨论C++11版本里面
提供的新的移动语义
在C++11之前
这个移动语义是不存在的
只有在C++11之后
才引入了新的移动语义
它主要是为了解决
深拷贝和浅拷贝的一些问题
我们首先来看
赋值操作符重载的一般形式
一般来讲 赋值操作符的重载
和以前的几种操作符的重载都不太一样
它最重要一个地方
就是将要返回
对本对象的一个引用
赋值嘛 就是要把另外一个数据
传给我们这个当前的对象
注意 当我们把这个赋值操作符
重载为类的成员函数的时候
它的this指针将指向我们这个本对象
而本对象将接受别的对象的值
这是赋值操作的一个最基本的语义
所以我们重载这个赋值操作符的时候
我们应该返回当前对象的一个引用
这样能够保证连续赋值的机制能够工作
这是非常重要的一个设计细则
参数嘛 当然传的是
对另外一个数偶对象的引用
我们看函数的实现
operator= 赋值的时候
只需要实现_a字段给_a字段
_b字段给_b字段 就OK了
想法其实是非常直接的
有一个特别的地方就是:if(*this==c)
this所指向的本对象
和c传过来的那个对象
如果两者在数值上是完全相等的
或者你认为它们是相等的
那么这样一个对象的赋值就没有意义
因为这两个量是一样的嘛
你还赋值它干嘛呢
赋值也不改变它的语义
也不改变它的结果
所以这个时候就可以不赋值
不赋值的结果
我们就直接return *this
赋值完了以后 我们要return*this
我们要引领this所指向的目标数据对象
这个就是我们的赋值操作符
我们必须按照这样一个模式进行操作
赋值 它其实就是一个拷贝
它把另外一个对象拷贝给我们这个本对象
这是非常重要的一个地方
回过头来看我们赋值操作符重载的语义
Couple& operator=
参数传constCouple& c
一定要按照这个方式来写
不按照这个方式来写
就和C++预定义赋值操作的语义
是不吻合的 记住这一点
再看实现 if( *this==c),return *this
赋值 return *this
一定要按照这个模式进行实现
有一个细节需要额外地说明一下
就是这个if( * this==c)的判定
要不要做的问题
如果你做了这个判定
并且当它满足条件的时候直接return*this
两个数偶类的对象是一样的
所以不需要赋值
那么它就不会产生额外的拷贝
但是你要注意
我们这里面多了一个if判定
所以实际上多了一个if语句
当这个对象的拷贝动作
在我们程序中需要频繁进行的时候
而且大部分时候
它们都不相等
这两个对象都不相等
所以做这个额外的判定
事实上没有增加我们的程序效率
反而降低了我们的程序效率
所以在某些场合
不判定这两个对象是不是相等
就直接拷贝 其实更合适
编程的时候要特别去注意这一点
接下来就是我们的复合赋值操作符
就是那个赋值操作符简写形式
我们加赋、减赋、乘赋、除赋、余赋
其实不仅仅这么多 还有呢
我们先看几个简单的例子
就用其中的加赋、乘赋
我们直接就用这个例子
只是一个示例
减赋、余赋、除赋
跟这个性质其实都一样的
当然我们在乘赋这个动作上边
我们同样重载了那个标量乘法的那个版本
我们也要把它写进去
Couple& operator+=(constCouple& c )
Couple& operator*=(constCouple& c)
Couple& operator*=(constint& k)
三个复合赋值操作符的重载
格式跟刚才非常得类似
返回值是一样的 参数一样的
除了这个标量乘法是个constint&
实现上也是类似的 operator+=
就把c类型的_a字段、_b字段
分别累加到_a、_b这两个字段上就OK了
然后return* this 做完了
加赋就这个事 乘赋 乘上去 OK了
标量乘 直接乘k
两字段都乘k 返回 * this 完事了
这个就是复合操作符的重载
实现上一样很简单
有一个很特殊的操作符
就是递增递减操作符
它实际上也是复合赋值操作符的一大类
只不过我们把它单独列出来
因为“++”、“--”都涉及到
对当前那个对象做一个加法或减法 对吧
它实际上也涉及到对本对象值的修改
所以它仍然算是一种广义的赋值
如果我们为这个数偶类
重载我们的递增递减操作符的时候
就要特别注意
因为那个“++”、“--”操作符
可以是前缀的 也可以是后缀的
不管是前缀的还是后缀的
它们都只有一个单操作数
也就是说它们是单目操作符
那么我们重载它的前缀递增和前缀递减
后缀递增和后缀递减的时候
我们怎么区分它的前缀和后缀的关系
所以为了区分重载的操作符
它到底是前缀操作符还是后缀操作符
你必须按照这个格式来写
Couple& operator++()
Couple operator++(int)
第一种方案表示我们重载的
是前缀操作符
第二种方案带一个单参数版本的
那表示我们重载的是后缀操作符版本
int后面跟着那个参数我们从来不用
它是那个操作符第二个操作数
有吗 没有
哪怕是后缀递增 它也没有 我们不用
写它唯一的目的是为区分
它和前缀操作符
前缀是无参数的 后缀是有参数的
这是前缀操作符和后缀操作符
重载时候的第一点差别
我们用一个参数来确定它是
前缀的还是后缀的
有参数就是后缀的
没参数就是前缀的 这是一个
第二个 返回值不一样
前缀操作符返回值是本对象的一个引用
所以写的是Couple&
后缀操作符返回的是本对象的一个拷贝
所以返回值是Couple
返回值类型是不一样的
同学们要特别注意这一条
这是C++要求的语义
“++”操作符、“--”操作符、前缀、后缀的原始实现
就是按照这个语义来实现的
所以当你重载的时候
一定要维持着“++”、“--”操作符的
原始的语义不变
这是我们重载这个操作符的格式
一定要记住
实现上就需要特别注意
前缀递增和后缀递增之间的差别
如果它是一个前缀的
那么我们在实现它的时候
++_a,++_b,return* this 就完事了
这就是一个前缀的
我就返回对本对象的一个引用
没有问题 就按照这个方式来写
可是如果是一个后缀的递增
那么就需要特别注意
就是我们首先要通过*this这个对象
来构造一个临时的Couple对象_t
一个局部量
我就用*this指针去拷贝构造它
拷贝构造之后
我们_a++,_b++
然后不能return*this
我们只能return _t
拷贝后的那个对象
这是后缀递增的语义
它是产生了一次替换
返回的不再是原始的那个对象
而是原始对象的一个拷贝
这才能够保证“++”操作符的运算
将在我们的这个表达式中
其它的表达式、操作符都算完了
它才最后算
先参与运算 后递增递减
不就是这个意思嘛
赋值操作符的返回值
有一点需要特别注意
除了后缀递增递减操作符之外
返回对本对象的一个引用
应该返回的是这个
最主要的目的就是和C++本身的语义
要完全吻合 这是一个
你如果真的返回一个对象
你就说我和语义吻合 行不行呢
其实也不是说不可以
但是你要特别小心
就是你真地返回这样的一个对象
它事实上是需要一个额外的拷贝构造的
你要构造一个对象 然后才能返回它
这个构造 当这个对象尺寸很大的时候
它实际上是对整个程序的效率
是有很大影响的
所以要特别注意这一点
如果你不需要使用返回值
来进行连续的赋值动作的话
你比如讲我把c=b=a
这个意思就是什么呢
就是要把a给b 然后把b再给c
把b赋值为a的那个结果然后再给c
它是个连续的赋值动作
如果你不想干这个事
那么这个返回值类型
实际上可以设成void 可以不写
还是刚才那句话
它就和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 编程实践
-第十五讲 网络编程--编程实践提交入口