量化软件——赫兹MT5智能交易系统项目 — C_Mouse 类

在上一篇文章《开发回放系统(第 26 部分):智能交易系统项目(I)》中,我们详细研究了第一个类如何开始构造。现在我们扩展这些思路,并令它们更实用。这就把我们带到了 C_Mouse 类的创建。它提供了最高级别的编程能力。不过,说到高级或低级编程语言,并不是在代码中包含污言秽语或行话。它有其它含义。当我们谈论高级或低级编程时,我们意指对于其他程序员来说理解代码是多么容易或困难。事实上,高级和低级编程之间的区别表明了代码对其他开发人员来说是多么简单或复杂。因此,如果代码与自然语言相似,则可视其为高级;如果代码与自然语言相似度较低,且更接近处理器解释指令,则视其为低级。

我们的目标是令类代码尽可能高级,同时尽可能避免某些类型的建模,那样可能会令经验不足的人难以理解。这是目标,尽管我不能保证它会完全达成。

C_Mouse 类:开始与用户交互
鼠标和键盘是用户与平台之间最常用的交互方式。因此,交互简单有效非常重要,这样用户就不必重新学习如何执行动作。代码从以下几行开始:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_MousePrefixName "MOUSE_"
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TV"
#define def_NameObjectLineT def_MousePrefixName + "TT"
#define def_NameObjectBitMp def_MousePrefixName + "TB"
#define def_NameObjectText  def_MousePrefixName + "TI"
//+------------------------------------------------------------------+
#define def_Fillet      "Resource\\Fillet.bmp"
#resource def_Fillet
//+------------------------------------------------------------------+

我们包含的一个头文件里包含了 C_Terminal 类。如上一篇文章所述,这个 C_Mouse 类文件与 C_Terminal 类文件位于同一目录之中,这令我们能够毫无障碍地使用这种语法。我们定义要包含在可执行文件中的资源名称,允许您挪动它,而无需再单独下载资源。这在许多情况下非常实用,尤其在使用过程中资源的可用性至关重要之时。我们通常将资源放在特定的目录中,从而便于访问。以这种方式,它就能始终与头文件一起编译。我们在 C_Mouse.mqh 文件所在的文件夹中加入一个名为 “Resource” 的目录。Fillet.bmp 文件位于 “Resource” 目录之中。如果我们在保持相同建模的同时更改目录结构,编译器会确切地知道在何处可以找到 Fillet.bmp 文件。一旦代码编译完毕,我们就能加载可执行文件,且不必担心找不到资源,因为它已被嵌入到可执行文件自身。

在此步骤中,我们首先定义一个名称,实际上它是稍后定义的其它名称的前缀。定义的用法令开发和维护变得更加容易,这是专业代码中的惯用做法。程序员定义要在代码中用到的各种名称和元素,这通常在 Defines.mqh 文件,或其它类似文件中完成。据此文件,可以轻松更改定义。不过,由于这些定义仅存在于该文件但中,因此无需在其它任何地方声明它们。

#undef def_MousePrefixName
#undef def_NameObjectLineV
#undef def_NameObjectBitMp
#undef def_NameObjectLineH
#undef def_NameObjectLineT
#undef def_NameObjectText
#undef def_Fillet

这段代码告诉编译器,由 C_Mouse.mqh 文件定义和可见的所有符号和名称,此刻都不再可见。通常不建议删除或更改其它文件中的定义 — 这不是惯用做法。这就是为什么我们在名称实际出现和使用的地方公布它们的原因。之后,这些定义将被删除。没有准则就更改或删除定义也非良好做法。如果我们需要在多个文件中使用一个定义,最好为它创建一个单独的文件。

现在我们继续类代码的前几行。这就是事情变得有趣的地方。一切从这里开始:

class C_Mouse : public C_Terminal
{

   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x01, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int      X,
                     Y;
            double   Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
      };

在这个片段中,我们看到 C_Terminal 类由 C_Mouse 类公开继承,这意味着使用 C_Mouse 类,我们就可访问 C_Terminal 类中的所有公开方法。因此,C_Mouse 类拥有的功能会比仅局限于 C_Mouse.mqh 文件中的代码更多。继承不仅提供了这种益处,还可以提高类的效率,我们将在以后的文章中讨论这些益处。我们继续操控该代码部分。在代码的受保护部分,我们有两个枚举声明,允许我们在稍高的级别进行编程。第一个枚举非常简单,遵循我们在上一篇文章中涵盖的相同概念和规则。另一方面,第二段清单也许看似有点令人困惑和复杂,但我们首先要探讨其复杂性的原因,以及它存在的原因。

该枚举为我们提供了一个机会,否则将更难维护;也就是说,它将为我们节省大量工作。此特定枚举创建名称定义,其等效于 #define 编译器指令。不过,我们决定使用枚举替代定义。这允许我们运用稍微不同的技术,但同时代码更容易理解。使用枚举,我们将看到代码如何变得更具可读性。这在复杂代码中变得至关重要。初看时,如果您认为该枚举声明非常困惑和复杂,那么您大概并未完全明白枚举的工作原理。从编译器的角度来看,枚举只是一连串定义,其中默认第一个元素从索引零开始。然而,我们可以为枚举设置所需的起始索引,编译器将从该值开始递增后续索引值。这在许多场景下非常实用,其中一个确定的索引值当作序列的起始值。通常,程序使用一长串枚举清单,其中的错误值是基于某些特定准则设置的。如果您定义了一个名称,并为其分配一个特定值,编译器将自动递增所有后续名称的值。这令创建大型定义清单更加容易,且不会在某些时刻出现重复值的风险。

事实上,许多程序员不用这种技术,这很令人惊讶,因为它可以极力避免某些类型的项目编程时出现错误。现在,您已经明白了这一点,您可以进行试验并发现,使用枚举可以极大简化创建大量相关元素清单(无论顺序与否)的过程。我们正在探索的方式旨在令代码更易于阅读和理解,从而改进编程。

下一部分是一个结构,负责通知代码的其余部分鼠标正在做什么。在这一点上,也许十分期望声明一个变量,但在类内部的私密子句之外声明变量,不认为是良好的编程做法。其他人也许认为,将这些声明放在代码的公开部分更为合适。不过,我更愿意从更受限的访问级别开始,允许公开访问仅作为最后的妥协。我们必须确保函数和方法拥有公开访问权限,但与类直接相关的函数和方法除外。否则,我们始终建议开始阶段给元素最低授权。

继续这个思路,我们看看 C_Mouse 类中存在的变量:


   private :
      enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
      struct st01
      {
         st_Mouse Data;
         color    corLineH,
                  corTrendP,
                  corTrendN;
         eStudy   Study;
      }m_Info;
      struct st_Mem
      {
         bool    CrossHair;
      }m_Mem;

特别有趣的是,我们已经有一个枚举,然其对类之外的任何其它代码部分都不可见。这是因为该枚举仅供该类内部使用,代码的其它部分没必要知晓它的存在。这个概念被称为封装,它基于这样的原则,即其它代码片段不会知晓如何实际执行给定任务的工作。这种类型的方式颇受函数库开发人员的高度重视,他们允许其它程序员在不透露函数库代码实际工作方式的情况下访问过程。

接下来,我们找到结构。这处用到了另一个可以在类主体之外访问的结构,我们将在解释访问过程和函数时详细讲述它。在这一点上,重点是要明白该变量指代一个私密类结构。还有另一种结构,而在这种情况下,我更喜欢使用这种方式,因为它清楚地表明内容是特殊的,且只在代码中非常特定的位置才可访问。不过,没有什么可以阻止您在以前的结构中声明相同的数据。在编写代码时,您只需要当心不要更改此数据,因为第一个结构中包含的数据是通用的,可以随时更改。

我们已经涉及到与变量相关的部分,现在我们能转入函数和方法的分析。请看以下代码:

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
   }

这段代码可促进软件重用,因为贯穿 C_Mouse 类的整个开发过程,我们需要创建各种元素,且必须遵守某些标准。因此,为了促进该过程,我们将把这个创建任务集中在一个方法之中。这种做法在声明中经常看到,尤其性能是一个关键因素时,就会使用特定的关键字。我之所以如此选择,是因为我希望编译器直接在声明代码的地方包含代码,其作用类似于宏。好吧,这也许会导致可执行文件大小的增加,但作为回报,我们将在运行时的整体性能方面获得改进,尽管很小。有时,出于各种因素,性能提升很小,这也许无法明断可执行文件大小增加是合理的。

在此,我们有一个状况,对许多人来说也许看似微不足道,但将成为贯穿代码反复出现的一个方面。此函数引用在 C_Terminal 类中声明的结构。不过,编译器并未将其解释为函数,而是当作一个常量变量。但这怎么可能呢?编译器如何把一个看起来像函数的声明当作常量变量?初看,这没有多大意义。不过,请更详细地查看这段调用代码,及其在 C_Terminal 类中的实现:

inline const st_Terminal GetInfoTerminal(void) const
   {
      return m_Infos;
   }

这段代码返回对属于 C_Terminal 类的结构的引用。我们返回引用的变量是类的私密变量,在任何境况下都不应被 C_Terminal 类内部以外的任何代码修改其值。为了确保代码在访问该私密变量时不会对其进行任何更改,我们决定包含一个特殊声明。以这种方式,编译器就可确保任何代码接收常量的引用,且无法更改其值。该措施用于避免意外更改或编程错误。因此,即使在 C_Terminal 类内部,尝试在函数中以不恰当的方式更改数值,编译器也会将此过程识别为错误,因为根据编译器的说法,没有任何信息可以更改。发生这种情况是因为该处不适合或错误地进行这种更改。

这种类型的编程虽然劳动强度更大,但提高了代码的健壮性和可靠性。不过,此上下文中存在一个缺陷,稍后会解决它。这是因为现在解释这一决定会令整体解释复杂化。现在我们看看 C_Mouse 类中的以下方法:

inline void CreateLineH(void)
   {
      CreateObjectBase(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }
 

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值