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

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

LinuxCPP1007在线视频

LinuxCPP1007

下一节:LinuxCPP1008

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

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

有一点是需要特别说明的

浅拷贝可能达不到我们的要求

当我们成员字段有一个指针的时候

大部分时候浅拷贝都是有问题的

这个时候我们就会使用深拷贝

我们也应该使用深拷贝

但是你要知道 深拷贝往往意味着

我们要重新构造一片存储区

而且目标对象

它的原始那片存储区往往又需要销毁

这里边频繁地产生动态的内存分配

与销毁的动作

要分配一片内存 后来又不用了 销毁

又分配一片内存 又不用了

很多时候 分配、销毁、分配、销毁的工作

完全是无用功

比如说程序里边

已经有了动态构造的一个对象

我构造这个对象的唯一的目的

就是为了挂到这个指针_p上

如果你使用一个深拷贝语义

你就必须重新再构造一片存储区

要new出来

然后一个元素、一个元素地拷贝过去

再把原始new出来的这个数组给它删掉

你注意我刚才的讲法了

我动态分配这个

数组存储空间的唯一的目的

就是为了把它挂接到_p上

而现在呢 我深拷贝语义

就会再产生数组的一个副本

然后拷贝 原来那个又不要了

这个额外动态内存分配有必要吗

没必要 那你可能就会说

这个时候我应该用浅拷贝啊

浅拷贝什么呀

如果它又不是这样的呢

浅拷贝不是不行么 对吧

这就涉及到一个最重要的问题

所以语言上边

应该给我们提供一个很特殊的机制

就是我能够把这个动态分配的对象

我们这里面就是4个元素的一个数组

要把这个动态分配的数组的所有权

移交给a这个对象的_p的字段

我们要做是这个事 这个叫移动语义

移动语义只在C++11之后才出现的

此前是不存在的

所以要特别去注意这一条

所以如果我真正地想完成

一个移动语义的构造

将意味着把a的_n字段赋值给b的_n字段

把a的_p字段赋值给b的_p字段

保证这两个字段是一模一样的

这样的话b的_p字段

将会同样持有目标数组的所有权

注意 你还要完成所有权的移交

所以a的_p那个指针必须赋值为0

赋值为NULL 赋值为空指针

这是必须要做的

这样才能完成所有权的移交嘛

否则的话不就双指针指向同一个地方

那一弄不就是程序崩溃了嘛

一delete 空悬指针就来了嘛

这是有问题的

所以我们移动语义做的就这个意思

俩对象构造出来没问题

但是我要求当拷贝构造和赋值构造的时候

目标数据对象的所有权

必须移交给我们的新对象

而原始对象将丧失那个所有权

_p指针将不再指向原来的那个数组

这个就叫移动语义

要想实现这个移动语义

我们就必须了解几个主要概念

一个是左值与右值

一个是左值引用与右值引用

然后我们才能考虑怎么实现

移动赋值与移动构造

并且讨论移动语义的成员函数重载的方式

我们首先来看左值与右值

在C语言里边

对左值和右值的原始定义是这样

可以出现在在赋值号左边或右边的

数据和数据对象

我们称它为左值

不仅仅数据对象 实际上还有函数之类

如果它能够出现在赋值号左边或右边

那么我们就说它具有左值

如果一个东西

它只能出现在赋值号的右边

那么我们就说它具有右值

有一个普遍的误解

就是左值只能出现在赋值号的左边

右值只能出现在赋值号的右边

这是一个普遍的误解

对于右值 这个结论是对的

只能出现在赋值号右边的东西具有右值

但是左值并不意味着

只能出现在赋值号的左边

它一样可以出现在赋值号的右边

你比如讲 我们看一个非常简单的赋值语句

x=x 左边的那个x

具有左值这是毫无疑问的

右边呢 右边那个x依然是左值

这是非常让人迷惑的地方

我们前面讲变量的时候

我们谈到过变量具有四个基本特性

VANT:值、地址、名称、类型

对吧 就这四个

编译完的代码呢

除非是面向对象的架构

我们下一讲将会讨论的

运行期型式信息

否则名字是没有的 类型也是没有的

就只有值和地址

值和地址和我们这里边

谈到的左值和右值有极大的相关性

但并不是一个东西

我们说值 我们说地址

是指从程序运行的这个角度来看的

值是在内存的某个地方的

里面的一个东西 这是值

而地址呢 是放东西的内存区域的编号

这是我们理解的地址和值的概念

这是从程序代码的这个角度来谈的

而左值和右值 实际上是编译器

对待我们标识符的角度来看的

当它认为这样的一个标识符

可以出现在赋值号左边或右边的时候

那么必须把它作为一个左值来对待

因为只有左值才允许这么去做

当它是一个右值的时候

它就只能出现在赋值号的右边

它不能出现在赋值号的左边

你比如说简单的一个整数 一个文字

字符串的文字 它是不可以被赋值的

所以它只能出现在赋值号的右边

它可以赋值给别人

但是它不能自我被赋值

被别人所赋值 这是不允许的

所以这样的量、这样的数据、

这样的文字

甚至这样的函数都只具有右值

不具有左值 而左值的量

它就有一个非常明确的地方

它可以被修改 出现在赋值号的左边

它就可以被修改 所以你看x=x

这个事情编译器要做几件事情

第一 取x的左值 取左边那个x的左值

这是第一步要做的事情

第二步 取右边那个x的左值

我们事实上取到了什么呢

取到了x这个量所对应的地址

然后去右边那个x所指代的地址数

去取它的右值

我们实际上取它什么呢 取到是x的值

然后把x的值赋值给x

也就是把这个值

重新放到这个内存区域里面去

它是这样的一个动作

这是三步动作

其实我们的C编译器

还做了最后一步动作

第四步动作 把这个刚刚放进去那个值

作为赋值表达式的结果返回

这是C的简单的一个赋值动作

所产生的效果

所以你看

这里边的左值和右值像这样一个关系

它和地址和值紧密相关

但并不完全相等

不是一类概念 这是第一个

我们需要描述清楚的地方

第二个 为了支持移动语义

关于左值和右值这个概念

在C++11版本里面做了一些变化

它跟过去不一样了

什么叫左值呢

左值是用于标识非临时对象

或者非成员函数的表达式

这种东西就叫左值

因为在C的里边没有引用传递

所以函数是不能返回一个引用的

函数不能返回引用

那它函数的返回值就是不可以用的

那么这样一个函数

它事实上不具有左值

这是非常重要的一个地方

我们可以取到那个函数的入口地址

但是那个函数本身

不能出现在赋值号的左边

但是有了引用传递之后

这个函数本身 一个函数调用本身

因为它的返回值是一个引用

所以它事实上

可以出现在赋值号的左边的

这样的函数就具有了左值

在引用传递出现之后的

非常重要的一个变化

而到了移动语义里边

对于左值和右值的定义又发生了变化

我们刚才已经说了左值的定义

那么右值呢

右值就是用于标识临时对象的表达式

或者与任何对象无关的一个值

这样的值我们称它为纯粹的右值

它就只具有右值的

这是一个重要的地方

或者用于标识即将失效的

一个对象的表达式

这是一个失效的值

它仍然具有右值 不具有左值

它马上就无效了 马上就没了

这样的一个量 这样的一个对象

它的表达式具有右值

这是一个非常重要的定义

左值和右值

然后就是我们进行函数调用的时候

传递的那个引用

在引入移动语义之前

C++里面 只有两种参数传递方式

一个是值传递 一个是引用传递

更不要说在引用传递引入之前

C/C++只有值传递一种

哪怕你使用指针作为函数参数传递

仍然是那个指针对象的值

这一点同学们一定是要记住的

我们即使是使用指针来传递它

传递的依然都是指针这个数据对象的值

它仍然是值传递 但是有了引用

我们传递的就不再是值

而是目标数据对象的一个引用

引用在函数内部只是充当

那个目标数据对象的别名

访问的其实就是原始的目标数据对象

这在C++11出现之前 这样的引用

事实上我们传递那个目标数据对象的左值

所以叫左值引用

为了支持移动语义

我们需要使用一个与原先的左值引用

不同的架构

这个架构 我们现在称之为右值引用

所以左值引用是用“&”记号

那么右值引用为了区分它

我们使用“&&”这个记号

为什么需要左值引用或右值引用呢

左值引用是为了修改目标数据对象的值

是为了以一个数学相一致的方式

去访问目标数据对象

那么又为什么需要右值引用呢

就是我们刚才谈到过的

你的深拷贝这个动作

需要频繁地分配和释放内存的

很多时候 这个分配和释放

事实上是没有必要的

它大大降低地降低了我们的程序效率

我们希望能够消灭

多余的内存分配与释放这样的动作

所以深拷贝 有的时候它的工作原理

我们不满意

移动语义的根本的目的

是为了完成那个目标数据对象的所有权的移交

在这个过程中

我们本身并不需要构建新的对象

所以也不需要重新地

调用构造函数和析构函数

这个事完全不需要做

所有权移交过去就行了

对象还是那个对象 对不对

移动语义的目的就是在这儿

为了和我们原始的构造函数相兼容

我的移动语义必须是采用引用的架构

这就是我们原来的构造函数——你看到的

都是必须使用一个引用

就是为了和我们数学式子相兼容

操作符重载的

都是引用——基本上都是引用

就是这个目的

所以你新的这个移动语义

也是为了构造一个新的对象

所以它的参数传递机制

还是应该是一个引用

不能够传指针 不能够传值

传指针 语义就变了

传值 就需要值的拷贝

就达不到移动语义的目的了

所以我们需要特别注意的就是

引用是必须的

但是 原先那个引用

我们现在称之为左值引用的那个东西

你也可以称它为普通引用 正常引用

叫普通引用或者左值引用

它传递的东西是左值

为什么要传递左值呢

是为了允许我们在函数内部

修改那个目标数据对象的值

所以要传递左值

这样的话它就可以出现在赋值号的左边了嘛

不是这个意思吗

如果你不想修改这个引用参数

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

那么你可以使用const去修饰它呀

限定它就完了嘛

所以一开始的时候 我们传递的是左值

能够让函数在内部修改

那个目标数据对象的内容

所以为了区分过去的左值引用

我们实际上现在的传递

只能传递那个对象的右值

要么你怎么区分

左值引用和右值引用呢 对吧

所以现在 我们只能传递我们的右值

让人迷惑的地方就在于最后一条

我们传递右值是干嘛的

我们其实仍然需要修改它所引用的

目标数据对象的内容

所以 为了保证能够在函数内部

修改目标数据对象的内容

你传递过来的右值在我们函数内部

实际上仍然会作为左值来对待

否则不能出现赋值号的左边 对吧

所以右值传进来

它会被作为左值在函数内部进行操作

如果它有左值的话

如果它真地没有左值

传过来的那个东西只有右值

那我们只能按照右值的方式去访问它了

对吧 那是另外一回事

所以这个地方就特别让人迷惑

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

LinuxCPP1007笔记与讨论

也许你还感兴趣的课程:

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