当前课程知识点:基于Linux的C++ >  第十一讲 泛型编程 >  11.13 泛型编程实践(五) >  LinuxCPP1113

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

LinuxCPP1113在线视频

LinuxCPP1113

下一节:LinuxCPP1114

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

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

第二种实现策略就是使用我们的函子

函子就是 function object

有时候也称函数对象 或者functor

我们称之为函子 函子的目的

在功能上就类似于函数指针

而在实现上呢

它是重载了函数调用操作符的一个类

同样地 当你想用它去比较

两个对象大小关系的时候

你必须确保你要比较的那两个对象

它们所从属的类上具有小于操作符

它们所从属的类上具有小于操作符

比较大小关系嘛

缺省的时候比较从小到大

需要一个小于比较操作符

你必须重载它 如果没有 你必须重载它

函子有一些特殊的优点

函数指针本身是不能够内联的

而函子可以

因为有的时候函子的比较

实际上非常非常简单

所以代码很简单

使用内联 整个程序运行效率能够提升

函数指针不能内联 而函子可以

这样的话 效率更高

第二 函子本身可以带有任意数量、任意型式的额外数据

第二 函子本身可以带有任意数量、任意型式的额外数据

可以保存我们的结果和状态

中间结果、最后的结果都可以

可以记录它的状态变化

我们代码的质量控制性、灵活性有了大大的提升

我们代码的质量控制性、灵活性有了大大的提升

第三个 编译时可以对函子进行型式检查

这个也是相当重要的一个地方

你说函数指针型它能不能够检查呢

它当然也能够检查

但是函子 它所能做的事情

显然要比使用函数指针所能做的事情

要多得多 也灵活得多

我们来看这道题怎么样用函子来实现

我们首先给出

函子的实现以后的使用方法

如果我有这样一个数组

当我想调用刚才那个 Min() 函数

求它的最小的元的时候

那么你就可以调用 Min()

第一个参数传数组的基地址

第二个参数传元素的个数

第三个传一个函子对象

因为这个函子对象仅仅用于

我们 Min() 函数的这次调用

所以我们就构造一个

匿名的函子对象就够了

不需要再给它起一个专门的名字了

那么我们就在这个函子类上

直接调用它的缺省构造函数

构造一个就可以了

这个函子类名字叫什么呢

叫 Comparer〈int〉 后面跟着一个“()”

就构造一个函子类的匿名对象

专门来处理整数比较的 就这个意思

我们现在来看我们的 Comparer〈int〉怎么实现

定义一个类模板

template〈typename T〉 class Comparer

{ public: bool operate()( const T & a, const T & b ){ return a < b; } };

{ public: bool operate()( const T & a, const T & b ){ return a < b; } };

这就是 Comparer 这个类

当然它是个类模板

我们待会才会讨论类模板

现在你看到的就是我们在这个类里边

我们重载了一个函数调用的操作符

它比较 a、b 两者的关系

当 a 小于 b 的时候 返回 true

否则返回 false

注意到这里边有一个非常重要的地方

就是它使用小于操作符来比较

a 和 b 这两个对象的大小关系

如果 a 和 b 这两个对象所从属的类是你自己定义的

如果 a 和 b 这两个对象所从属的类是你自己定义的

那么你要想使用小于操作符

你必须保证这个类上重载了小于操作符

否则它怎么调用小于操作符啊 对不对

所以你必须确保 a 和 b 所从属的那个类

也就是 T 这个型上有小于操作符

如果没有 这段代码是不能工作的

有了它 我们可以修改 Min() 函数

template〈typename T, typename Comparer〉

Comparer 这时候它是一个型的名字了

我们就使用一个比较对象

这样一个型 Comparer

我们的函子来作为模板的第二个型式参数

在内部 像函数一样使用这个 Comparer 对象

传进来的是一个对象

我们在对象上重载了函数调用操作符

所以可以让这个对象

像一个函数一样被调用

Comparer( a[i], a[index] )

出来的结果:true 和 false

就按照这个方式进行调用

这就是函数调用操作符定义或使用

就按照这个方式编写代码就可以了

接下来的这个概念就是完美转发

我们前面讲右值引用的时候

其实已经谈到过

我们说为什么 C++11 里边

要引入右值引用呢

它就是为了实现我们的移动语义

我们当时又说了

实际上没有右值引用

不也一样可以实现移动语义吗

那么右值引用的意义又何在呢

就在这里 完美转发

就是它的一个最重要的使用场合

对一个库的设计者来讲

他需要设计一个通用的函数

将接收到的参数传给其它的函数

这个过程叫参数的转发

这在库的设计上是非常有用的一个特征

但是 在没有右值引用的情况下

这个参数是没有办法完美转发的

所谓完美转发

是指所有的参数在转发以后

保持原先的语义不变

它原来是 const 转发后的结果还是 const

原来是一个引用 转发后的结果还是一个引用

原来是一个引用 转发后的结果还是一个引用

原来是一个指针 转发后的结果还是一个指针

原来是一个指针 转发后的结果还是一个指针

原来是个左值 转发以后还是一个左值

原来是个左值 转发以后还是一个左值

原来是个右值 转发以后还是一个右值

原来是个右值 转发以后还是一个右值

没有右值引用 这个转发是做不到完美的

没有右值引用 这个转发是做不到完美的

也就是说 这些属性不可能全部保存下来

所以我们才有了右值转发

有了右值转发以后

因为能够完美地保存原始参数的型式特征

因为能够完美地保存原始参数的型式特征

所以我们的构造函数事实上都不需要写那么多

所以我们的构造函数事实上都不需要写那么多

我们原来讲 我写一个类

既要提供移动语义 又要提供拷贝语义

那需要写几个函数啊

4 个 俩构造函数 俩重载的赋值操作符

对吧 一个成员要写 4 个

你要记住这一点

两个成员 你就要写 8 个

非常多 组合爆炸了 没办法弄

所以有了右值引用 有了完美转发

这样的构造函数我们事实上不需要写那么多了

这样的构造函数我们事实上不需要写那么多了

完美转发有一个固定的实现的流程

当你需要同时提供移动语义

与拷贝语义的时候

你要重载大量的建构函数 我们的构造函数

编程工作量大 容易出错

那么我们使用右值引用和我们的函数模板相结合

那么我们使用右值引用和我们的函数模板相结合

就可以实现完美转发

极大地降低我们的代码编写量

看一个例子

假设我们有一个 class A 这个类

这个类里边呢带有两个数据成员

都是 string 类型 _s,_t

我要构造它们的拷贝语义和移动语义

这里边就有讲究了

第一个字符串可能是移动语义

第二个字符串可能也是移动语义

第一个字符串移动语义

第二个字符串拷贝语义

第一个字符串拷贝语义

第二个字符串移动语义

第一个字符串拷贝语义

第二个字符串拷贝语义

你看见 是不是 4 个

构造函数要写 4 个呀

每一个都可能是移动语义、拷贝语义

你可不就要写 4 个吗

相对应地 我没有写 operate=

重载的赋值操作符

你不一样要重载 4 个吗

这里边可不就 8 个函数要写嘛

多费劲啊

有了完美转发和模板完美地配合在一起

我们只需要写一个函数模板 就 OK 了

我们写 template〈typename T1, typname T2〉 A( T1 && s, T2 && t )

我们写 template〈typename T1, typname T2〉 A( T1 && s, T2 && t )

初始化列表里边

_s 初始化成 std::forward〈T1〉(s)

_t 初始化为 std::forward〈T2〉(t)

构造函数一个 够了

前面要写 4 个 现在我们写一个 完事了

写了一个模板 完事了 写完了

那么当你真地在构造这个对象的时候

它会发生什么事情呢

它就会根据实际参数的型式

替你生成不同的构造函数

这个构造函数的版本

可能是一个左值引用

也可能是一个右值引用

全看你实际参数的情况

它会把它接受过来的 s 或 t

这两个形式参数

根据它的特性(它到底是一个左值呢

还是一个右值呢 是个左值引用呢

还是个右值引用呢)

根据这个特性自动地来转发给 _s 和 _t

这两个 string 对象的构造函数

做的就是这个事情

在这个例子里

T1 和 T2 本身可以是不同的型

我们这里面只不过它们都是 string

所以型刚好是相同的

这仅仅是示例

平时用的时候 并不要求它们是相同的型

当编译器进行实参推演的时候

(你真地构造这个对象 它就要做实参推演)

它会使用所谓的引用折叠机制

来处理传递的实际的参数的性质

所谓引用折叠 讲起来很复杂

同学们只需要记住一句话就行了:

当型式参数是右值引用的时候

也就是 T&& 这种型的时候

当且仅当实际参数为右值或右值引用时

传递的实际参数才是右值引用

否则传的就是左值

明白这个概念就可以了

引用折叠机制与 const、volatile

这样的关键字是无关的

你有这个东西 它就保持

没有 它就没有 注意这个概念

std::forward() 同样是标准库里面

替我们实现的一个转发函数

它就能够完成转发

它的性质和刚才那个 move() 函数非常非常类似

它的性质和刚才那个 move() 函数非常非常类似

它转发的就是 T 的一个右值引用

不管 t 这个型是什么

它转发出来的结果就会把它转换成 T &&

就是它的一个右值引用传过去

函数内部呢 接收到一个右值引用

然后它才能决定它到底用的是右值还是左值

然后它才能决定它到底用的是右值还是左值

那是另外一回事

这是引用折叠机制

所以如果你想提供一个移动语义

那么你就传递右值引用进去

如果你想提供拷贝语义

你就传递它的一个左值引用进去

这就 OK 了

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

LinuxCPP1113笔记与讨论

也许你还感兴趣的课程:

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