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

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

LinuxCPP1008在线视频

LinuxCPP1008

下一节:LinuxCPP1009

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

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

我们来看移动赋值与移动构造的

这样一个实现

为了实现这个移动语义

我们这个拷贝构造函数

和重载的赋值操作符

和原先的那个深拷贝动作就不一样了

拷贝构造函数是 A( A && that )

这是一个右值引用

双 “&” 记号表示右值引用

而不是左值引用了

A & operator=( A && that )

这是一个右值引用的赋值操作符重载

返回值呢 仍然是这个对象的左值引用

不是右值引用 注意就在这里

看看我们怎么实现它

第一个拷贝构造的实现

that 是我们传进来的那个对象

那个参数的前面写的是 A &&

为了保证在函数内部它能够修改 that 所引用的那个目标数据对象

为了保证在函数内部它能够修改 that 所引用的那个目标数据对象

虽然你是以右值引用的形式传递进来的

但是在函数内部

它实际上仍然被当做左值来对待

所以在这个地方写的这个 that

它实际上是个左值 它不是右值

传的是右值引用没错

但是在内部是把它当做左值来看的

所以这个地方是个左值

我们的赋值操作符 A & A::operator=( A && that )

内部的代码实现

_n = that._n, _p = that._p, that._n = 0

that._p = nullptr(null pointer,空指针)

return *this

这就是我们的移动赋值语义

我要把 that 的数据成员

一个一个地拷贝到我们的 *this 里

然后让 _p 指针做一次所有权的移交

*this 的 _p 字段将会指向那个目标数组

而 that 的 _p 字段将会变成空指针

不再指向那个目标数组

形成这个所有权的移交

所以你看到在这个赋值的操作符里边

目标引用的那个数据对象 that

它的内容将会被修改

你不把它当成左值 它能修改吗

它修改不了的 对吧

所以非常重要的一个地方

这就是完成我们的移动语义

这里面有一个地方需要特别解释

就是 null pointer 空指针 nullptr

在 C++11 里边

给我们预定义的一个新的关键字

它表示空指针类型

它的类型是 nullptr_t 这种类型的一个对象

它的类型是 nullptr_t 这种类型的一个对象

已经帮我们定义好的

你就直接用就行了

这个空指针类型 nullptr

可以隐式转换成任意指针类型和 bool 类型

但是你不能把它转换成整数类型

特别注意这一条

定义它的目的是为了取代 NULL 这个宏

你不编译 C++11 的代码

那么实际上你就不需要

也不能使用这个关键字 nullptr

就直接使用老式的 NULL 也是可以的

如果你不使用这段代码

你使用我这条注释掉的这段代码

*this = that

我告诉你 它是不能工作的

它不会调用我们这个重载的赋值操作符

因为在函数的内部

that 是左值而不是右值

that 是左值

那就意味着你可以传左值

你可以传左值的引用

但是你不能传右值的引用

你不能把 that 传给 A && that

这不可以 这是一个右值引用

那是左值 不能传给它的

参数类型是不匹配的 编译器会报错

除非你实现了一个左值引用版本

否则的话 编译器是通不过的

可是如果你实现了左值引用

那不就是深拷贝了嘛

它就不是我们的移动拷贝了嘛

对吧 它就不一样了

所以这一条语句是不可以的

和我们刚才讲的拷贝构造

或者是一个赋值操作符重载一样

你可以在这里面测试 that

那个目标对象和 *this 是不是相等的

如果是相等 你可以不赋值

还是我前面那个说法

就是这个测试很多时候

并不能够给我们提升程序效率

事实上反而会降低我们的程序效率

所以测不测试看你的程序是不是需要它

这是我们的移动赋值与移动构造

它具体的实现代码很短

但是可解释的地方其实是很多的

因为毕竟是 C++11 中新引入的知识

我们特别需要说明

如果这样一个对象

有的时候我需要深拷贝

有的时候我需要移动拷贝

有的时候我需要深拷贝赋值

有的时候我需要移动赋值

那怎么办呢

就是说这两个语义我都需要

那么当你在实现这个类的时候

你就必须为它重载

两个版本的构造函数和赋值操作符

两个你都得提供

我们现在习惯于按照这样的一个方式

同时提供它的拷贝语义和移动语义

一个 A( const A & that )

一个 A( A && that )

第一个当然是左值引用

对 const A 的左值引用

第二个就是对 A 的右值引用

这是两个版本

第一个版本 左值引用版本完成我们的深拷贝语义

第一个版本 左值引用版本完成我们的深拷贝语义

第二个 右值版本完成我们的移动语义

第二个 右值版本完成我们的移动语义

这两者是不一样的

因为我们拷贝是不需要修改

目标引用的那个内容的

所以传 const A & 就 OK 了

不需要 A &

因为我们右值引用是需要修改那个目标数据对象的值的

因为我们右值引用是需要修改那个目标数据对象的值的

所以事实上不应该传递 const A &&

所以提供两个版本

赋值操作符也一样

A & operator=( const A & that )

这是第一个 深拷贝版本

第二个 A & operator=( A && that )

这是一个移动赋值版本

两个版本你都要写

所以我们这里面要写几个函数 4 个

2 个构造函数 2 个重载的 operator=

那我们看我们怎么用它

我们现在来看这段代码

我首先构造一个对象 a

4 个元素 然后把它每个元素值分别初始化成 1、2、3、4

4 个元素 然后把它每个元素值分别初始化成 1、2、3、4

4 个整数 我们用这个数组来构造对象 a

然后我使用拷贝构造来构造对象 b

传的那个参数是刚才的那个对象 a

所以它调用的是拷贝构造

它会把 a 这个型解释成对那个对象 a 的一个引用

它会把 a 这个型解释成对那个对象 a 的一个引用

用 const 表达它是一个不会在构造函数内部

用 const 表达它是一个不会在构造函数内部

修改目标数据对象的值的这样一个引用

所以它相对于引用的版本将是 const A &

这是一个拷贝构造

如果是你这么写:b = a

那么它会调用普通的赋值操作符

就是我们刚才重载的那个普通赋值

深拷贝的那个版本去完成它的拷贝动作

这是 b = a 要做的事

如果你想调用它的移动赋值或移动构造那个版本

如果你想调用它的移动赋值或移动构造那个版本

那么就需要这么写

A c( static_cast〈 A && 〉( a ) )

你必须按照这个方式来写

必须把 a 静态转型成对它的右值引用

必须把 a 静态转型成对它的右值引用

然后用右值引用去构造那个对象 c

这个时候它才能调用那个右值引用版本去进行移动构造

这个时候它才能调用那个右值引用版本去进行移动构造

如果你不做静态转型

就是我前面讲的

它就会调用它的深拷贝版本

因为它认为那个东西是个左值引用

它不是右值引用

所以它就会调用它的左值引用版本

而不调用它的右值引用版本

特别注意这一条 赋值也一样

你必须写 c = static_cast〈 A && 〉( a )

你必须按照这个方式赋值

它才能够调用它的移动赋值版本

移动语义和深拷贝语义

如果混合在一起编程的时候

两者都需要同时提供

那么在调用的时候就需要特别小心

你传递的那个引用

到底是一个左值引用还是一个右值引用

将会决定它到底调用的是个深拷贝版本

还是一个移动版本

特别注意这一条

基于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文件

LinuxCPP1008笔记与讨论

也许你还感兴趣的课程:

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