事件编程(二)

2016-01-29 12:28 10 1 收藏

事件编程(二),事件编程(二)

【 tulaoshi.com - C语言心得技巧 】

C++ At Work 专栏...
事件编程(二)

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com)

原著:Paul DiLascia
翻译:NorthTibet

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com)

下载源代码:CAtWork0603.exe (3,178KB)
原文出处:Event Programming, Part 2


  在本文的第一部分(事件编程一),我回答了一个关于用 C++ 实现本机事件的问题。讨论了一般意义上的事件并示范了如何用接口为你的类定义事件处理器,事件的处理必须在客户机实现。我的实现有一些缺陷,我承诺过最终要解决掉,本文就来完成这件事情。
  在开始之前,先简单回顾一下前面写的那个程序,PrimeCalc。如 Figure 1 所示:


Figure 1 计算素数

  程序中使用了一个计算素数的类 CPrimeCalculator,这个类发起两个事件:Progress 和 Done。当搜索到素数时,该类触发 Progress 事件以报告目前发现了多少素数。完成处理后触发 Done 事件。这两个事件都是由接口 IPrimeEvents 定义的:

class IPrimeEvents {public:  virtual void OnProgress(UINT nPrimes) = 0;  virtual void OnDone() = 0;}; 
  客户机要想处理事件必须得从 IPrimeEvents 派生,实现事件处理函数,并调用 CPrimeCalculator::Register 来注册其接口。CPrimeCalculator::Register 会将客户机对象/接口添加到其内部列表。当触发了一个 Progress 事件时,CPrimeCalculator 便调用辅助函数 NotifyProgress:
void CPrimeCalculator::NotifyProgress(UINT nFound){  list::iterator it;  for (it=m_clients.begin(); it!=m_clients.end(); it++) {    (*it)->OnProgress(nFound);  }} 
  NotifyProgress 遍历客户机列表,调用每个客户机的 OnProgress 处理函数。当某个程序员使用 CPrimeCalculator 时,编写事件处理代码很容易——只要从 IPrimeEvents 派生并实现处理器即可。但是在实现这种触发事件的 CPrimeCalculator 类机制时冗长乏味。你必须得为每个事件(如 Foo)实现诸如 NotifyFoo 这样的函数,即使处理模式一模一样。事件触发代码被划分在两个类中,事件接口 IPrimeEvents 和 事件源 CPrimeCalculator。如果你想将同样的事件接口用于不同的事件源那该怎么办?IPrimeEvents 是很通用,我可能将它改名为 IProgressEvents 并将它用于任何以整数形式报告处理进度的类并在完成处理时用 Done。但每个触发 Progess 事件的类必须重新实现触发事件的通知函数。理想情况下,所有事件代码都应该放在单个类中。
  既然通知函数在本文中是一种实验模型,那么自然会问这样的问题:它们有没有某种通用的实现方法?我能将整个事件机制封装到单个的类、模板或宏,或者任何事件源能使用的其它什么类型中吗?答案是肯定中的肯定。我将示范如何创建一个使用宏和模板的事件系统,以便将事件处理的代码量降至最低限度。我们的旅程需要借助一些高境界的 C++ 操作,比如嵌套模板以及仿函数类(functor class)。
  我将分几个步骤实现这个系统。目的是编写一个实现通知函数 NotifyProgress 以及 NotifyDone 的模板。每个函数都具备相似而又不完全一样的模型:
// NotifyFoo — raise Foo eventlist<IPrimeEvents*::iterator it;for (it=m_clients.begin(); it!=m_clients.end(); it++) {  (*it)-OnFoo(/*args*/);} 
  也就是说迭代客户机列表,并针对每个客户机调用 OnFoo,传递事件参数。如何把它写成一个模板呢?可以将接口 IPrimeEvents 参数化为一个类型 T,但如何参数化事件处理函数 OnFoo,程序员可能选择的任何名字和签名。
  任何时候你参数化某个函数时,都应该考虑:仿函数,也叫做 functor。仿函数是 C++ 语言中将函数转换为类的一种机制,它代替了给回调函数传递指针的做法,而是传递仿函数类的实例。在标准模板库 STL 中包含有丰富的 Functor,并实现了一些使用 functor 的算法,尤其是 for_each 算法,在本文中很有用:
for_each(m_clients.begin(), m_clients.end(),NotifyProgress(nFound));

  for_each 算法从头到尾迭代容器元素,并对每个元素调用函数对象 NotifyProgress。这里说的“函数对象”到底是指的什么呢?不是一个函数,它是一个对象。这个类看起来像下面这个样子:

class NotifyProgress {protected:  UINT m_nFound;public:  NotifyProgress(UINT n) : nFound(n) { }  void operator()(IPrimeEvents* obj)  {    obj->OnProgress(nFound);  }};
  NotifyProgress 实现函数 operator()(IPrimeEvents*),它是 for_each 算法需要的东西。一般来讲,如果你具备一个类型为 T 对象集合,for_each 会需要一个

来源:https://www.tulaoshi.com/n/20160129/1486206.html

延伸阅读
VisualBasic以友好易学的可视化开发环境闻名于世,成为人们学习计算机编程的首选语言。目前,全世界大概有300多万人使用着VisualBasic语言。如果您想在这茫茫众生中出类拔萃,那么您就不得不学习API(ApplicationProgramlnterface,即Windows的应用程序编程接口)编程。不懂API,那可成不了高手。 第一节:API基础 API说到底就是一系列的...
标签: Delphi
  10.3.2.2 服务器程序的编写 服务器程序必须包含对 DLL 的调用代码,如: function GetGlobalMem: THandle; far; external 'c:\dlls\glbmem'; 通过调用该函数,服务器可以获得全局内存块的句柄。 在写入数据前,服务器必须锁定全局内存,以避免在写入过程中 Windows 移动该内存块的位置。 函数 Gl...
一.通过鼠标在屏幕上的移动来控件程序界面 本例通过鼠标在屏幕上的移动来控制程序窗体的显示与隐藏:当鼠标移动到窗体所在区域时窗体显示,反之隐藏起来。仅需一条API函数:GetCursorPos。注意:如果需要将API函数置于模块中请对代码作相应修改。要尝试本例,需给标准EXE工程缺省添加一个Timer控件。 PrivateTypePOINTAPI ...
标签: Delphi
  2.1.4 跳转语句 Object Pascal的跳转语句有 if 和 case两个。 2.1.4.1 if语句         if语句会计算一个表达式,并根据计算结果决定程序流程。在上文的例程中,根据 ColorDialog.Execute 的返回值,决定窗体的背景颜色。 if 保留字后跟随一个生成 Boolean 值 True或 False 的表达式...
在组件编程中对事件的理解是十分重要的,C# 中的事件是当对象发生某些有趣的事情时,类向该类的客户提供通知的一种方法。与事件联系最为紧密的,个人认为是委托.委托可以将方法引用封装在委托对象内。为了弄清组件-事件-委托三者的关系,本人用实际的例子来谈 谈小弟的理解。 首先创建一个Windows控件项目,添加如下控件样板。 ...

经验教程

421

收藏

30
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部