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

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

LinuxCPP1117在线视频

LinuxCPP1117

下一节:LinuxCPP1118

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

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

接下来一节

我们提供一个工程实践的样例

就是事件机制

什么叫事件呢

事件是指操作系统和应用程序中

发生了某件事情

程序的某一个组件需要响应这个事件

并进行特定的处理

这个处理的方案和方式就叫事件机制

在面向对象的架构中

事件响应函数最可能的是成员函数

没有面向对象技术之前

怎么处理这个事件呢

我们只有一个方案 使用函数指针

有了面向对象架构以后

事件响应函数最可能的就是成员函数

那这就会导致一个问题

就是指向类的成员函数的指针

它不能转型成哑型指针

这一点是和普通的函数指针是不一样的

因为它不仅仅包括了那个成员函数的特性

因为它不仅仅包括了那个成员函数的特性

还包括了那个类的特性

所以指向类成员函数的指针和哑型指针

不能够自如地互相转化

不能转型 那就有问题

因为我们响应这个事件的

将会是某一个对象的成员函数

而这个对象从属于哪个类

不同的时候是不一样的

成员函数名字也有可能是不一样的

只要那个对象不一样

这个事情就大条了

对吧 你就处理不了了

转成哑型指针肯定是不成了嘛

所以这里面会有一个大的问题

同时更严格讲起来

其实你这个指向类成员函数的指针

不仅不能转型成哑型指针

它也不能转型成另外一个类的成员函数的指针

它也不能转型成另外一个类的成员函数的指针

有的编译器允许你在类的继承层次上面

将指向类的成员函数的指针进行转型

如果没有继承层次呢

它也不能转型 再说这个事儿

也不一定所有的编译器都支持

所以这实际上是一个很成问题的地方

那么我们怎么实现呢 有一个诀窍

就是当你用对象无法实现的时候

那么你就用指向对象的指针来实现它

当你用指向对象的指针无法实现的时候

那么你就用指向对象的指针的指针

来实现它 你就应该用

指向指向对象的指针的指针来实现它

用一个二级指针

这是非常重要的技巧

所以在我们 C++ 里边

实现事件机制的时候

那么我们在工程实践的场合

需要借助于模板的概念

形成我们的事件委托模型

我会定义一个 Event 一个类模板

用它来管理我们的事件响应者对象

实现事件多播

我们有一个 EventResponsor 这样的一个类模板

我们有一个 EventResponsor 这样的一个类模板

它对应于响应者对象与响应者行为的配对

它对应于响应者对象与响应者行为的配对

也就是事件响应者是谁

它的响应行为是哪一个

这就形成一个配对

我们用 Empty 这样一个空类

来做委托模型和指针转换

我们来看具体的代码实现

空类 “class Empty { };” 什么也没有

它就用于指代我们的响应者对象

我们刚才不是讲了吗

你不能使用指向类成员函数的指针

对吧 我们的响应函数是明确的

但是你不能做这样的转型

所以我们需要使用一个空类

来保存那个指向类的成员函数的指针中的对象那部分

来保存那个指向类的成员函数的指针中的对象那部分

接下来这个类模板就是事件响应者

template〈typename EventAction〉 class EventResponsor

它带的一个模板的形式参数是 EventAction

事件活动 每一个事件响应者模板

它都保存着特定事件的响应者和它对应的响应行为

它都保存着特定事件的响应者和它对应的响应行为

所以它包括两个字段

这两个字段为了处理的方便

我们都把它公开了

一个是指向 Empty 类的一个指针 actor

就是行动者

一个是指向 EventAction 的一个指针 action

这就是它的响应行为

为什么要这么设计呢 刚才那么讲的

一个普通的函数指针转型成一个哑型指针是没有问题的

一个普通的函数指针转型成一个哑型指针是没有问题的

可是一个指向类的成员函数的指针是不能够转型为哑型指针的

可是一个指向类的成员函数的指针是不能够转型为哑型指针的

为什么呢

就是因为指向类成员函数的指针

它不是普通的指针

首先它有一个指向那个类的对象的指针标记

首先它有一个指向那个类的对象的指针标记

其次它有那个成员函数

在那个对象中的那个属性的描述

它实际上在实现上都不一定是指针

尺寸都有可能是变化的

所以这实现策略和哑型指针

是完全不一样的两回事

你不能够把它转型

那么我们这里面要做的一件事情

就是在 EventResponsor 里边记录

记录什么呢

你不能记录指向类成员函数的指针

但是我可以记录指向指向类成员函数的指针的指针 这个就是它

但是我可以记录指向指向类成员函数的指针的指针 这个就是它

我记录那个指向类成员函数指针的地址

而不是它真实指向的那个类的成员函数

EventAction * action

action 记录的就是它

而 actor 记录的就是那个对象

哪一个行动者做这件事儿

做的就是这个对象

当我们最终响应这个行为的时候

我们将在 actor 这个对象上做这个 action

就用这两个属性完成最终的

事件响应机制的响应者行为和响应者对象的配对和拼接

事件响应机制的响应者行为和响应者对象的配对和拼接

对 EventResponsor 来讲

它有一个缺省的构造函数

actor 把它初始化化成 NULL

action 也初始化成 NULL 空值

还有一个二值构造函数

EventResponsor( Empty *, EventAction * )

就直接拷贝就完了

EventResponsor 这个类并不负责

action 和 actor 这两个数据字段的存储问题

虽然它们是指针

但是 EventResponsor 并不负责它

我们会有一个专门的事件类来处理这个指针问题

我们会有一个专门的事件类来处理这个指针问题

EventResponsor 本身并不负责

这两个指针的动态分配与释放的问题

跟它没关系

有一个友元函数 operator==

判定两个事件响应者是不是相等的

注意我们比较的时候是不一样的

如果是 actor

只要比较两者是不是相等就行了

因为它们都是指向那个对象的指针

所以只要比较两者值是不是相等

就能够得到它们是不是指向同一个对象

而后面那个 action 呢 存的是二级指针

是指向指向类成员函数的指针的指针

所以我们要比较的时候

必须先引领 然后再比较

才能看它们指向的是不是同样的一个类的成员函数

才能看它们指向的是不是同样的一个类的成员函数

注意这个实现

就这一点是要注意的地方

这是我们的 EventResponsor

然后是我们的事件类模板 class Event

前边的模板是 template〈typename EventAction〉

还是事件活动

Event 我们定义两个型

因为我们是使用一个向量

来构造所有响应同样的事件的响应者的

一个事件的响应者有很多个的

所以我用一个向量来保存它

vector 这个类型太长了

vector〈〉里面是什么呢

〈〉就是那个事件响应者的类型

事件响应者的类型是什么呢

那玩意儿也是一个模板

所以你得写 EventResponsor〈EventAction〉

你看你写的 vector〈 EventResponsor〈EventAction〉〉

很费劲的 很长的一串

每次都那么写 烦死人

所以我们用 typedef 给它定义出来

形成叫 EventResponsors

表示一个事件响应者集合

叫事件响应者们 好吧

我们就用它来替换这么长的一个类型的标记

我们就用它来替换这么长的一个类型的标记

你如果觉得这个不够长

那你看下面这一个呢

后面还有 “::iterator” 呢

你还要取它的迭代器呢

这就是事件迭代器

那不就更长了嘛

所以我们也把它定义出来

有一点是需要说明的

就是早期的 C++ 编译器

它没有办法解析连续的模板右箭头

它会认为这是一个输入操作符

它会搞混淆的 所以必须加空格

C++11 以后的编译器都能够处理这个问题

不加空格也行了

有的时候 在模板中

我们很难决定一个特定的名字

它到底是一个型还是一个量

所以如果你想明确地

告诉编译器这是一个型 而不是一个量

可以在这个型的名字前边加上 typename

有一个虚析构函数

用一个事件迭代器去迭代这个事件向量

一个接着一个的删除它的事件响应者 就完了

删除它的响应者的时候

因为事实上我们只动态构造一个东西

就是 action 的那个指针

所以我们只要销毁它就行了

它析构的时候 剩下的向量本身

由这个事件这个类自动地替我们析构

最重要的两个函数来了

第一个 事件绑定

在我这个事件类里边

我使用一个向量来保存这个特定事件的所有响应者

我使用一个向量来保存这个特定事件的所有响应者

那么每一个响应者来了以后

就要绑定到这个对应的事件上

将来这个事件发生以后 它才能够响应

就是插入到我们这个向量的列表里边去

那么 Bind() 这个函数就应该这么写

它同样是有一个模板

template〈typename Responsor, typename Action〉

传两个模板的形式参数

一个是 Responsor 响应者

一个是响应者的活动 Action

它们的类型

注意和我们前面那个并不完全匹配的

它也并不需要完全匹配

这只是模块的形式参数

void Bind( Responsor * actor, Action action )

那个活动 本身就是一个指针了

它是指向类成员函数的指针

所以不需要再用指针来了

我们传进来不需要

我们内部会 new 它的

看我们的代码实现

把 act 定义成指向 Action 的一个指针量

然后 new 一个 Action

用小 action 去构造它 去初始化

构造一个指向指向类成员函数的指针的指针

然后我们通过传进来的 actor

和新构造出来的这个 act 来构造一个

EventResponsor〈EventAction〉类的一个对象 er

要把 actor 转型成 Empty *

要把 act 转型成 EventAction *

然后构造这个对象 er

定义一个 bool 量 unbound

设为 true 就是未绑定

一开始是未绑定的 迭代

查找这个事件向量的列表

一个接着一个地去比对

看看我们新构造的这个 er 对象

是不是在我们的事件向量里边

已经存过一次了

什么时候知道它存了还是没存呢

那么就在循环里边

你迭代的时候看 *it 和 er 是不是相等的

*it == er

我们不是重载了 operate== 吗

所以 *it == er 就表示这个事件响应者对象

已经被我们在向量列表中绑定进去了

已经有了 actor 是一样的

它对应的 action 也是一样的

那可不就已经有了嘛

所以这种时候 我们把它 unbound 设置成 false 我就 break

所以这种时候 我们把它 unbound 设置成 false 我就 break

就不用再朝下做

如果是 unbound 没有绑定

我们就把这个新的这个 er 对象

插入到我们的向量里

否则就销毁我们新构造的这个 act 内存区域

否则就销毁我们新构造的这个 act 内存区域

这是我们的 Bind() 函数

相对应的是 Unbind()

我要解除事件绑定 模板还一样

template〈typename Responsor, typename Action〉 void Unbind( Responsor * actor, Action action )

template〈typename Responsor, typename Action〉 void Unbind( Responsor * actor, Action action )

同样地 我要 new 一个 Action 出来

用 act 表达它

构造一个 EventResponsor〈EventAction〉类的一个对象 er

构造一个 EventResponsor〈EventAction〉类的一个对象 er

同样要做转型构造 同样我们需要迭代

同样我们需要去查找

当我找到 *it==er 的时候

说明我们需要将这个对象

从事件响应队列里边给它摘出来

解除绑定嘛 把它摘出来

所以我们要 delete it->action

然后把 this->_ers.erase( it )

把这个迭代器所指向的这个对象

从向量中给擦掉 我不要了

删除它 然后 break

不管怎么样 最后要删除我们

新构造出来的这个 act 那个对象

这个就是我们的 Unbind() 函数

代码看上去有点小复杂

Event 这个类最后只有一个私有的数据成员

EventResponsors _ers 事件响应者们

某一个特定的事件

它有一个对应的事件响应向量

这里边包括了很多个事件响应者

每一个事件响应者

都会对这个事件做出响应

只要你把那个事件响应者

绑定到这个事件里来

就是插入到这个事件这个类的那个对象

EventResponsors _ers 里去

就能够做到这一条

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

LinuxCPP1117笔记与讨论

也许你还感兴趣的课程:

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