当前课程知识点:基于Linux的C++ > 第十一讲 泛型编程 > 11.18 泛型编程实践(十) > LinuxCPP1118
好 我们现在来看怎么用它
我们首先定义一个新型 EventDelegator
用它来作为事件委托者型
然后我们定义一个指向类的成员函数的指针型 ValueChanged
然后我们定义一个指向类的成员函数的指针型 ValueChanged
它需要带两个参数
一个参数是 value 一个参数是 tag
value 就是那个值发生变化以后
它的那个新值
tag 就是一个附加的参数
这是一个指向类成员函数的指针
所以我们前面要给它定义好它的类
这个类就是我们的事件委托者类 EventDelegator
这个类就是我们的事件委托者类 EventDelegator
接下来是 Trigger 触发者类的定义
在触发者类里 我们只需要定义一个
私有的数据成员 _value
用它来保存这个对象的一个特定的值
一个 _value
这个值如果发生变化
它就会产生一个值发生变化的事件
也就是所谓的值变更事件
所以我们又定义了一个公开的属性 value_changed
所以我们又定义了一个公开的属性 value_changed
当值发生变化的时候 这个 value_changed
这样的一个值发生变化的属性里边
所有的事件响应者都必须做出行动
所以 value_changed 这样的一个属性
我们要使用 Event〈ValueChanged〉
这样的一个事件型对它进行定义
ValueChanged 就是 Event 这个事件类模板的实际参数
ValueChanged 就是 Event 这个事件类模板的实际参数
我们就用它定义 value_changed
这一个值变更事件的公开属性
为什么要把它定义成公开的呢
理论上来讲 这么定义不是不太好吗
这是一个属性
但实际上是为了方便我们应用
如果要严格地保持它的数据封装与信息隐藏的话
如果要严格地保持它的数据封装与信息隐藏的话
那么事实上我们最好的方案是把它定义成 protected
那么事实上我们最好的方案是把它定义成 protected
类的定义是简单的
现在我们来实现
它最重要的一个函数 SetValue()
我们最难的地方来了
trigger 这是一个触发值变更事件的一个对象
当它设定它的 _value 值的时候
它就会产生一个值变更事件
在这个时候
所有响应这个值变更事件的对象都必须做出行动
所有响应这个值变更事件的对象都必须做出行动
这个行动什么时候触发呢
当然在这个值被设定的时候触发
所以 SetValue() 就将负责
所有事件响应者的响应行动的那个调用
这是 SetValue() 要做的事情
首先 当这个新的 value 值和原值是相等的时候 我们什么也不做
首先 当这个新的 value 值和原值是相等的时候 我们什么也不做
这个值没有发生变更
所以我们什么都不做
否则的话我们将设定新值
将 _value 赋值为 value
然后我们要得到
所有响应这个值变更事件的事件响应者列表 也就是那个 vector
所有响应这个值变更事件的事件响应者列表 也就是那个 vector
这个 vector 存在什么地方呢
存在我们的 Event〈ValueChanged〉这个类的
一个对象里 那个对象在哪里
就在我们的 Trigger 类里那个公开的属性 value_changed 那里 响应者呢
就在我们的 Trigger 类里那个公开的属性 value_changed 那里 响应者呢
就是那个向量里的每一个元素
所以我们通过 this 这个指针
访问 value_changed 这个公开的属性
然后调用 GetResponsors()
得到那个事件响应者向量
我们这里使用了两行代码
是因为我这一行实在是写不下了
那个对象的型是 Event〈ValueChanged〉::EventResponsors
那个对象的型是 Event〈ValueChanged〉::EventResponsors
所以我们定义 Event〈ValueChanged〉::EventResponsors 的一个对象 ers
所以我们定义 Event〈ValueChanged〉::EventResponsors 的一个对象 ers
所以我们定义 Event〈ValueChanged〉::EventResponsors 的一个对象 ers
把 ers 赋值为 this->value_changed.GetResponsors()
把 ers 赋值为 this->value_changed.GetResponsors()
这样的话
我们就得到了这个事件响应者向量
接下来做什么
在这些事件响应者上调用它们的事件响应行为
在这些事件响应者上调用它们的事件响应行为
所以如果这个向量非空
那么我们就定义一个迭代器 it
这个迭代器的型当然是 Event〈ValueChanged〉::EventIterator
这个迭代器的型当然是 Event〈ValueChanged〉::EventIterator
我们定义这样的一个迭代器
迭代 每找到一个事件响应者
我们就调用它的事件响应函数
响应我们这个值变更事件
循环做的就是这个事
问题是怎么调用
这是我们事件机制里最难的一个位置
it 是什么
it 不是那个事件响应者对象
it 相当于指向那个事件响应者的指针
那个事件响应者是什么
是 EventResponsor 对吧
我们在这个向量里存的是 EventResponsor
当然它依然是一个类模板
所以实际上存的是 EventResponsor〈ValueChanged〉
所以实际上存的是 EventResponsor〈ValueChanged〉
响应这个值变更事件的事件响应者们
存的是这个东西
每个元素都是 EventResponsor〈ValueChanged〉
*it 就指代那个事件响应者
而 it 是指向它的 记住这一点
每一个事件响应者里有两个成员
一个是 actor 一个是 action
一个是指向响应这个行动的那个对象的指针
一个是指向响应这个行动的那个对象的指针
一个是指向指向响应行动函数的指针的指针 对吧
一个是指向指向响应行动函数的指针的指针 对吧
我们当时实现事件机制的时候
就是按照这个方式实现的
我们怎么访问事件响应者呢
it->actor 得到的就是那个 actor 字段
我们怎么访问它的行动呢
it->action 得到的就是那个 action 字段
it->action 是什么
it->action 是指向指向类的成员函数的指针的指针
it->action 是指向指向类的成员函数的指针的指针
我们怎么得到指向类成员函数的指针 引领
我们怎么得到指向类成员函数的指针 引领
所以我要在 it->action 前边
加一个 “*” 引领
得到指向类的成员函数的指针
然后我们就想调用它
这个 *(it->action)
是指向类的成员函数指针没错
那么它到底是在哪一个对象上调用呢
它在 it->actor 那个对象上调用
所以你要想得到 it->actor
这个对象上的成员函数的指针
那么我们必须用 “->*” 这个模式去得到它
那么我们必须用 “->*” 这个模式去得到它
所以严格的写法就是 (it->actor)->*(*(it->action))
所以严格的写法就是 (it->actor)->*(*(it->action))
这才得到指向 actor 这个对象的那个类的成员函数的指针
这才得到指向 actor 这个对象的那个类的成员函数的指针
也就是说 严格讲起来
实际上是在 actor 这个对象上
调用指向它的类的成员函数的指针
我们想做的其实就是这个事
所以你看 这里最复杂的地方
就是 (it->actor)->*(*(it->action))
特别得费劲 用起来很复杂
这里面一堆括号
(it->actor)->*(*(it->action))
一堆括号 太费劲了
其实这些括号都可以不写 为啥呀
因为这几个操作符的优先级很碰巧
优先级最高的是 “->” 符号
其次是 “*” 号 再次是 “->*” 号
所以这里面的括号其实都不需要写
现在我们得到的就是那个 actor 对象的指向类的成员函数的指针
现在我们得到的就是那个 actor 对象的指向类的成员函数的指针
我们要在这个基础之上调用它的成员函数 怎么调用呢
我们要在这个基础之上调用它的成员函数 怎么调用呢
在外面封装一个括号
这样你就可以把它当做一个普通的函数一样用了
这样你就可以把它当做一个普通的函数一样用了
然后我们后边传两个参数 value、tag
完成这个调用
最外面这两个括号是不能省略的
里面的那些括号都可以省略
这个就是我们事件机制的调用
非常非常费劲
你看 SetValue() 这个函数
每设定一个新值 它就会遍历我们这个事件响应者的向量
每设定一个新值 它就会遍历我们这个事件响应者的向量
然后一个接着一个地调用里边事件响应者的成员函数
然后一个接着一个地调用里边事件响应者的成员函数
去响应这个事件 做的就是这个事
接下来是 Actor 行动者的定义
行动者要做三件事情
一个 它要负责侦听这个事件
有一个成员函数叫 Listen()
它需要传递一个 trigger
trigger 是指向触发器的一个指针
我们要侦听这个事件
那么这个事件由谁导致的呢
当然是触发器
我们就侦听这个 trigger 对象的事件
就是这个意思
所以我们要在 trigger 上
调用它的 value_changed 这个属性的 Bind() 成员函数
调用它的 value_changed 这个属性的 Bind() 成员函数
将 Actor 这个行动者绑定到它的那个值变更事件上
将 Actor 这个行动者绑定到它的那个值变更事件上
所以它需要传两个参数
一个是它自己 this
第二个就是响应这个值变更事件的那个事件响应函数的入口地址
第二个就是响应这个值变更事件的那个事件响应函数的入口地址
传的就是 &Actor::OnValueChanged
把自己和响应那个事件的自己的那个成员函数绑定到触发器对象上
把自己和响应那个事件的自己的那个成员函数绑定到触发器对象上
这样的话就把这个事件响应函数
写到了那个触发器对象的事件响应列表里
这是 Listen()
Unlisten() 就是取消这个侦听动作
我不听了 我不管了 就这个意思
所以它的具体的实现和 Listen() 是一样的
也只有一行代码
只不过是把那个 Bind() 成员函数的调用
换成了 Unbind() 成员函数的调用
第三个就是值变更事件的响应函数 OnValueChanged()
第三个就是值变更事件的响应函数 OnValueChanged()
它的实现就比较简单了
我们只需要简单地输出一行信息就完了
它那个附加参数 viod * tag 传的是什么呢
传的就是额外的字符串信息
所以我们在这里边要把它转换成 char *
把它输出 很简单就完成了
这是 Actor 行动者
我们看主函数 在主函数内
我定义一个字符串 “Now the value is”
然后定义一个触发器对象 t
定义两个行动者对象 a1 和 a2
然后在 a1 上调用 Listen()
在 a2 上调用 Listen()
传的参数都是 Trigger 那个对象 t 的地址
也就是说 现在我们有两个行动者将负责响应
也就是说 现在我们有两个行动者将负责响应
那个触发器对象 t 的值变更事件
那么我们现在就为 t 这个对象
设定一个新值 10 我们传递 s 字符串
在这里要把 s 字符串首先做一次常量转型
在这里要把 s 字符串首先做一次常量转型
把它的 const 给去掉
然后再做一次复诠转型
把它转成哑型指针作为附加参数传进去
虽然连续两次转型看上去很讨厌
也很麻烦
但是这是最科学最安全的方法
接下来我们 a2.Unlisten()
取消 a2 这个对象的侦听动作
这样的话 a2 这个对象
将从 t 这个对象的事件响应者列表中被删除
将从 t 这个对象的事件响应者列表中被删除
我们重新设定一次新值
然后你就能够看到
当第一次设定新值的时候
有两个对象去侦听它的值变更事件
而当取消 a2 这个对象的侦听之后
那么再次设定新值
将只有 a1 一个对象响应这个值变更事件
将只有 a1 一个对象响应这个值变更事件
这就是我们事件机制的全部实现
同学们一定要掌握它
尤其是指向类的成员函数的指针的用法
在一些复杂的程序架构里边
没有它是很难实现的
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口