当前课程知识点:基于Linux的C++ >  第十讲 操作符重载 >  10.9 赋值操作符重载(五) >  LinuxCPP1009

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP1009在线视频

LinuxCPP1009

下一节:LinuxCPP1010

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP1009课程教案、知识点、字幕

有几个地方是需要跟同学们

特别解释的

一个地方就是

如果我真地只需要实现

刚才那个例子里边的那个移动语义

其实左值引用 它本来就可以完成

为啥呀 引用本身就意味着

可以修改目标数据对象的值

你移动语义的目的

不就是把那个东西拷贝过来

同时把那个指针(那个所有权)

给我转交过来吗 对吧

我之所以在左值引用参数列表里面

前面加一个constA&

变成对一个constA的一个引用(左值引用)

目的无非是表明

我这个函数内部不想修改它

如果你想修改它

你把const去掉不就完了吗

它不就能修改吗

而我们移动赋值

移动拷贝要的不就是修改吗

你想达到这个修改的目的

const去掉就可以做到

如果你想同时提供这两种语义

如果你同时想提供移动语义

和拷贝语义的话

那么事实上你就重载两个版本就行了

一个是const版本

一个是非const版本 就完了

一样是可以做到这一点的

如果它是一个const

那我们就调用const版本

如果它不是const

我们就调用它的非const的版本

如果它是非const

我们又想调用它的const版本

把它转型成const

然后再调用就完事了 对吧

非const版本移动语义

const的版本拷贝语义 没问题

对于我们程序来讲这是两个不同的函数

有没有const

它的函数签名是不一样的

所以是两个不同的函数

重载是完全合法的

可以这么做 没有问题

你看我们的实现 这是一个移动版本

所以我们要修改目标数据对象的值

看看_n被改了

_p被改了 对吧

这是一个const的版本

所以我们深拷贝 赋值操作符也一样

这是一个移动赋值动作

所以要修改目标引用的内容

这个呢 是一个深拷贝的动作

所以跟刚才那个实现一样

要new的 很简单哪

我们左值语义就能够搞定它

左值引用就够了 要右值引用干嘛呢

就是为了实现这个移动语义

弄了一个很奇怪的模式去来实现它

事情当然不是那么简单

看看我们的主程序

我们刚才那个例子怎么用

首先 我定义一个量a1

我直接写的Aa1

因为后面没有任何参数传进去

所以它是一个缺省构造

const Aa2 它依然是一个缺省构造

但是它是一个常量 不是变量

这是不同的地方

所以我缺省构造出一个常量来

然后再也不变了

我们这里面只是一个示例

这事很奇怪 很少见 对吧

但是它本身是合法的

然后我用a1去构造a3

这是一个拷贝构造

传的是一个A类的对象

所以它会调用A::A(A&)

也就是我们的移动构造语义

传的是A& 所以它会移动构造

意味着你可以修改目标引用的值

所以它会调用移动构造那个版本

a2呢 是个constA&传进来的

你传的是constA的这个对象嘛

所以引用传递 传的就是const A &

那么它就会调用A::A( const A & )这个函数

这个构造函数是什么呢

就是我们实现的深拷贝语义

所以它就会深拷贝

第一个移动构造 第二个拷贝构造

看 区分开了吧

那么你可能就会说

我想构造这个对象a5

我以a1这个对象来构造a5

但是我想进行移动构造

而不是拷贝构造

如果你写a5(a1) 那就是移动构造

它不是拷贝构造

如果我想拷贝构造 怎么办呢

就有一个招 const_cast( a1 )

跟那个static_cast一样

它也是一个类型转换的关键字

具体的解释我们也要

在下一节里边去讨论

现在我们就需要知道

如果这样一个量 它不是const

我们可以通过const_cast这样一个转换

临时把它转换成一个const

它原来是变量 我用const_cast这个转换

就能够临时把它转换成一个常量

如果它原来是一个常量

我就可以通过const_cast转换

临时把它转换成一个非常量

就是取消或设置这个量的const属性

临时地做一下 我们这个目的

就是为了把a1转换成const A 而不是A

然后调用它那个拷贝构造版本

因为你如果传a1 它就是移动构造

你只有传const A类的对象

它才能够进行拷贝构造

你又想拷贝构造 它本身又是变量

所以只能临时给它加上const

表示它是常量

所以现在我们要拷贝构造

你看我们有右值引用的时候

我们要做什么事情 static_cast

现在呢 我们要做const_cast

有差别吗 没差别

那边要折腾一下 这边也要折腾一下

从劳动强度来讲 基本上差不离 对吧

它能做到这一条

再看下面的代码

我又定义了三个量:a6、a7、a8

把a6赋值为a1,a7赋值为a2

a8赋值为const_cast(a1)

把它转型成常量 然后赋值给a8

跟我们的构造函数的性质是一样的

这是赋值操作符重载 对吧 一样的

你看 没有右值引用 只有左值

这个移动语义也有了 拷贝语义也有了

都在嘛 一样是4个函数啊

跟刚才左值引用、右值引用

混合着用不一样吗 都是四个函数

你说那我现在搞了一个右值引用

来实现移动语义 有意义吗

左值引用就能够搞定的事嘛

那么如果你仅仅这么理解

那么就意味着你事实上

对我们的引用语义 尤其是右值引用语义

就没有了解得很深刻

它的意义、目的并不仅仅体现在这里

虽然最初是想实现拷贝语义

但其实并不仅仅是为了实现拷贝语义

就像我刚才讲的 不用它

一样可以实现拷贝语义

右值引用的第一个意义是

右值引用可以使用文字

作为函数的实际参数

这是一个细微的

但是很有有趣的改变

你比如讲我有一个函数 叫int f(int& x)

这是一个左值引用的版本

所以return++x

它可以把这个结果带回去的

引用嘛 可以把结果带回去的

++x 那个实际对象的值就改了 对吧

它就会变加1了嘛

然后我们返回加1后的值

这个函数本身很简单就一条语句

但是你要特别注意

这个函数 它不接受文字类型的参数

因为什么呢

因为我们获取不了那个文字的左值

我们后面看代码 你就明白了

第二个版本int f(int&&x) 传右值引用

内部仍然是return++x

右值引用 传这个 对吧

这个时候因为我们传的是右值

所以实际上

它接受文字作为实际参数 这是可以的

因为它传递的就是右值引用

在函数内部呢 就是我刚才讲的

有个基本规则

具名的右值引用在函数内部

将会作为左值来看

匿名的右值引用 它将作为右值来看

理论上是这样 但是看代码

如果有一段代码是这么写的

std::cout<< ++10 <

那么这个代码编译器是不接受的

通过不了 因为什么呢

10 它是一个整型的文字

这个文字只有右值没有左值

它能赋值给别人

它绝不能够被别人所赋值

所以++10这个语句本身是非法的

编译器是不接受的

没有左值就不能“++”

这是肯定的一点

编译器通不过 明白了吧

我们可以调用这个函数std::cout<< f(10)

10是一个文字 作为函数的参数传进去

我们实际上只能传那个文字的右值引用

而不能传它的左值引用

这是需要特别明确的地方

没有左值啊 引用什么呢 对吧

只能传递它的右值引用

在函数内部

我们只能以右值的方式来对待它

刚才不是说了吗

传一个右值引用进来

在函数内部是当作一个左值引用

来对待的呀

它是有限制条件的

就是我们要尽可能地

把它当作左值引用来对待

是因为我们要修改目标数据对象的值

所以限制条件就是

我前面说的具名右值引用作为左值

而匿名右值引用作为右值

因为10 它是个匿名的右值引用

10是没有变量的名字的——它不是变量

它仅仅是个文字 它是没有名字的

所以它是个匿名的右值对象

匿名的右值数据

那么这样的话 它就是个匿名右值引用

那么在我们的函数内部

f这个函数内部只能作为右值

如果作为右值 ++x

它就是非法的 做不了

只有左值 才能++x 右值做不了

好嘛 编译器会告诉你

这个代码也是错的 理论上是这样

有的编译器会给你输出11

就把那个1加到10上面

然后给你个11

什么编译器会得到这个结果呢

我就不说那个编译器的名字

你回去看一看 多试几个编译器

有的时候就会给你一个

极其奇怪的结果

理论上不应该的 但恰恰就出现了

更重要地

右值引用的最主要的目的其实是这个

主要是避免编写过多的构造与赋值函数

同学们首先要记得

不管是左值引用还是右值引用

如果你想要同时提供

我们的拷贝语义和移动语义

那么你至少需要2对(4个)

构造和赋值函数的

构造函数一个 赋值函数一个 这是一对

必须要成对出现的

你至少需要提供2对

一个移动版本的

一个是拷贝版本的 对吧

还有 如果你这个类里边

有很多指针对象成员

或者其他对象成员

而且这些对象成员涉及到

指针所有权的问题

那么你可能需要或者你可能想为

这些指针成员中的每一个

都提供一个移动语义和拷贝语义

这事情就要命了 你知道吧

一个指针量 我需要4个函数(成员函数)

俩移动语义的 俩拷贝语义的

需要写4个 这是一个指针

俩指针我要写几个呢 我要写8个

因为第一个指针拷贝 第二个指针也拷贝

第一个指针拷贝 第二个指针移动

第一个指针移动 第二个指针拷贝

第一个指针移动 第二个指针也移动

你看 拷贝拷贝、拷贝移动、

移动拷贝、移动移动

组合爆炸 写不过来啊

所以参数如果很多

而且都需要涉及到

移动语义和拷贝语义的混合编程

这里构造函数

写起来能累死你 太费劲了

没有右值语义 这个问题搞不定

光有右值语义也搞不定

必须要会我们下一讲要讲的泛型编程

否则的话你搞不定这个题

最后一个

它实际上用于实现一个完美转发

什么叫完美转发呢

完美转发的意思就是

完成一个参数从一个函数

到另外一个函数的传递 这叫转发

一个函数在运行期间

它会接受一个参数

它内部可能不做处理

也可能只做简单处理

然后把参数传递给另外一个函数

转发对库的设计可能是很重要的

这样的一个机制

在右值引用出现之前

我们就没有一个完美转发过程

不管你使用什么样的逻辑来实现它

这个转发总是有一些缺陷的

我们转发最好能够做到什么程度呢

就是接受什么样的值

就传出去什么样的值

而没有右值引用 这个事是做不到的

右值引用的最主要的目的

第二个目的 就在这里

至于怎么样实现完美转发

我们下一讲讲泛型编程的时候

会再讨论它

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP1009笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。