
了解 MQL5 面向对象编程(OOP)
概述
在本文中,我们将分享编程中最重要的主题之一,其可顺滑且轻松地进行编码,并帮助我们应用 DRY(Do Not Repeat Yourself — 不要自我重复)的概念,作为开发人员或程序员的捷径。额外还能提升所有创建的软件和其它功能的安全性。我们将讨论面向对象编程(OOP),以及如何运用这一概念以 MQL5(MetaQuotes 语言)进行编码,首先要了解 OOP 的基础知识,然后通过查看一些应用程序来理解如何使用 MQL5。
由此,我们将通过以下三点来讨论这个有趣而重要的话题:
本文的主要标的是了解面向对象编程(OOP)的一般基础知识,以及它如何在软件创建中发挥作用。从中,我们将学到如何以 MQL5 应用这种方式,以便可以帮助我们通过应用这种令人难以置信的方式来创建更有效、更安全的软件。
对任何人来说,学习有关 OOP 方法的更多信息都非常实用且非常重要,有关该方法的资源普遍,特别是因为它适用于其它编程语言,如 C++、Java、Python、等等。对于 MQL5 语言,非常重要的资源不仅是在本文的主题当中,而且存在于 MQL5 文档的每一个主题当中,您可以浏览 MQL5 OOP 文档 链接查看 OOP 主题。
什么是 OOP
在本主题中,我们将大致了解 OOP 的基础知识,如此我们从定义 OOP 开始这个有趣的主题。OOP 是面向对象编程(Object-Oriented Programming)的缩写,它是计算机编程的范式,它有助于顺滑地创建和开发可重用的软件,无需重复工作和代码,这有助于我们应用 DRY(Do not Repeat Yourself —不要自我重复)的概念。
正如我们将看到的,OOP 帮助我们更贴近世界的本质,因为我们周围的一切都是由物体创造的,每个物体都有各自的性质和行为,这就是我们在软件中可以处理的数据。如果我们想更深入 OOP,我们需要知道我们将应对的对象和类,对象意即类的实例,类是对象的模板。在类的模板内部,我们详细定义了这个对象的行为。
OOP 的基本概念:
当我们在软件中应用 OOP 时,将遵守以下原则。
- 封装。
- 抽象。
- 继承。
- 多态。
1- 封装:
封装是能够在一个类中链接函数和数据的方法,类中的数据和函数可以是私密的,只能在类内访问,也可以是公开的,可以从类的外部访问。封装概念有助于隐藏类实现的复杂性,并令开发人员能够完全掌控其数据,有助于跟踪所有其它依赖的数值,且不会发生冲突。
由此,我们可以说,封装有助于保持我们的系统正常运行,并避免许多可能的错误,此外还为开发人员提供了高等级的控制,助其在测试和处理类数据时更顺畅、更容易,且不会影响或更改软件的整体代码。根据上述内容,它还有助于解决错误,并防止编码太复杂。
下图示意封装概念:
2- 抽象:
抽象是隐藏不必要的细节,且仅呈现基本细节的方法。它比封装概念更广泛,但它有助于达成相同的目标,即保护数据和实现函数,无需了解所有类的实现过程,仅需知道我要做什么才能完成实现。
为此,我们需要包含两个重要的方法:接口和实现。接口是类交互和彼此呼应的方法,而实现则是代码的所有细节、或类逻辑细节的方法。因此,抽象有助于提升软件的安全性,也有助于不重复编码过程、或从头开始重新编码,而仅需基于已创建的应用程序来开发和编码更多应用程序。
3- 继承:
顾名思义,继承概念意味着我们从旧类派生出一个新类,而新类继承了旧类的特征,在这种情况下,旧类称为父类或超类,新派生出的类称为子类。这个概念有助于应用 DRY(不要自我重复)的概念,且提供了可重用性的主要思想。
4- 多态:
顾名思义,如果我们将这个单词分成两个部分,我们会发现(Poly)意即“许多”或“多个”,(Morphism)意即形式,故多态概念意味着它能够帮助一个实体以多种形式表达,例如,如果我们有一个 Sum 方法,它可以表现出不同行为,好比一个能够获得(a)与(b)变量之和的方法;而另一个也可以得到变量的求和,但参数不同,如(a)、(b)和(c)。
简言之,多态性意味着一个接口和许多或多个方法。
我们可以总结一下我们提到的内容,即 OOP 是一种计算机编程模型,它专注于通过具有独特行为和性质的对象来组织软件的设计,这在大型复杂软件中是一种很实用的方法,尤其若是该软件的更新和操作类型众多。
OOP 属性和特征:
- 应用 OOP 方式的软件包括类和函数。
- 通过应用封装和抽象原则隐藏了数据,安全等级得以提高。
- 它有助于轻松处理复杂的项目,因为代码可以分成小的代码模块,这样就可以降低项目的复杂度。
- 更新和开发过程更容易。
- 通过应用继承原则实现代码的可重用性。
- 拥有在不冲突的情况下创建同一类的多个实例的能力。
我们可以找到许多有能力应用 OOP 方式的编程语言,最流行的是 C++、C#、Python、Java、JavaScript、PHP、等等。不光这些语言,还有 MQL5 语言我们也可以应用 OOP 方法,这就是我们要通过以下主题学习的内容。
MQL5 中的 OOP
在本主题中,我们将学习 MQL5 中的 OOP,以及如何运用它。就像我们提到的 OOP 的基础知识一样,我们创建一个类,就像一个对象的蓝图;类本身是一个集合,或包含变量和方法,它们与函数相同,但它在类中被称为方法,用于执行一套特定的任务。这里还值得一提的是,类中的变量和方法或函数称之为类成员。我们在此要说的是,在 MQL5 中应用 OOP 概念是一项良好且值得赞赏的努力,因为不光品质和安全性,还像我们提到的那样,它会节省大量精力工作。
如果我们需要在 MQL5 中应用 OOP 概念,我们需要了解如何在 MQL5 中运用以下内容:
- 类
- 访问修饰符
- 构造函数和析构函数
- 派生(子)类
- 虚拟函数
- 对象
类:
在 MQL5 中,如果我们需要创建一个类作为对象的蓝图,我们需要在全局范围内声明这个类,如同函数一样。我们可以通过 class 关键字后跟所需的唯一值来创建这个类,然后在两个大括号 “{}” 之间或内部,放置作为类成员的变量和方法,然后在第二个大括号 “}” 之后,我们放置一个分号 “;" 来终结类声明。顺便说一句,我们可以在程序或包含文件中使用这个类声明。
下面是这个类声明的示例:
class Cobject { int var1; // variable1 double var2; // variable1 void method1(); // Method or function1 };
正如我们在前面的例子中所见,我们的类有三个成员,它们是两个变量和一个方法或函数。
访问修饰符:
通过这些访问修饰符,我们可以判定从类外部我们可以用到哪些变量和函数,我们有三个访问关键字,分别是 public(公开)、private(私密)、和 protected(受保护)。
- public:表示可从类之外使用的成员。
- private:表示不能在类之外使用,且只供类内部函数使用的成员。该类的子类不能继承这些私密成员。
- protected:表示可由子类继承的成员,但对于非子类,它们本质上是私密的。
如果我们需要查看一个示例,我们可以看看下方这个:
class Cobject { private: int var1; // variable1 protected: double var2; // variable1 public: void method1(); // Method or function1 };
正如我们在前面的例子中所见,在类中我们有三个成员,前两个是变量:一个是私密的,另一个是受保护的;第三个是公开的函数。
构造函数和析构函数:
如果我们需要初始化类中的变量,我们要用到构造函数。如果我们不这样做,它将默认由编译器创建,但此默认构造函数将不可见。且据访问性惯例,它还必须是公开的。另一方面,析构函数是当类对象被注销时自动调用的函数。我们调用析构函数时,可以用波浪号(~)加上相同类名。无论析构函数是否存在,字符串、动态数组和对象都需要逆初始化,如此这般它们无论如何都会被逆初始化。
下面是构造函数的示例:
class CPrices { private: double open; // Open price double high; // High price double low; // Low price double close; // Close price public: //--- Default constructor CPrices(void); //--- Parametric constructor CPrices(double o,double h,double l, double c); };
派生(子)类:
正如我们之前学过的,继承概念是 OOP 最有价值和最实用的特征之一,因为我们可以据超类或父类创建一个子类,并且这个子类继承了父类的所有成员,但私密成员除外。之后,我们可以为该子类添加新的变量和函数。
如果我们需要看一个示例,我们可以看看下面的示例,如果我们有一个价格父类,我们能据其创建一个每日价格子类,如下所示:
class CDailyPrices : public CPrices { public: double open; // Open price double high; // High price double low; // Low price double close; // Close price };
正如我们所见,父类名称是 CPrices,而 CDailyPrices 是子类或派生类。现在,我们发现 CPrices 的所有公开成员和受保护成员都是 CDailyPrices 类的一部分,并且它们仍然是公开的。
虚拟函数:
如果我们想在子类中更新方法或函数的操作方式,我们可以通过在父类中声明虚拟(virtual)函数来做到这一点,然后在子类中定义函数。例如,如果在基类中我们有两个不同版本的函数。对于父类,我们用 virtual 关键字定义函数
class CVar { public: virtual void varCal(); };
然后,我们在子类中更新同名函数
class CVar1 : public CVar { public: int varCal(int x, int y); };
对象:
对象有唯一的标识符,如同我们在创建变量时所做的那样,我们对象标识符之前用类名作为其类型。我们可以根据项目需要创建许多属于我们类的对象,所有我们要做的就是为每个对象起一个独有的标识符。声明对象后,我们可以使用点(.)访问任何公开成员。
我们看一个示例,就能清晰地理解,如果我们创建一个类,该类含有交易次数(num_trades)的整数型变量
class CSystrades { public: int num_trades; };
然后我们需要创建一个属于这个类的对象,称为 system1,我们将执行如下相同的操作来做到这一点
CSystrades system1;
然后,我们可以为这个对象赋值(3),如下所示
system1.num_trades=3;
现在,我们围绕本主题,学习了 OOP 方式的一些最重要思想,了解如何在 MQL5 中应用它。
OOP 应用
在这个有趣的部分中,我们将介绍一些在我们的软件中运用 OOP 方式的简单应用,从而更好地理解我们如何使用它,以及它有多大用处。
priceClass 应用程序:
在这个简单的应用程序中,我们需要检查多个时间帧的价格,在这个示例中,我们将在这里展示三个时间帧(日线、周线和月线),我们还需要查看所有价格(开盘价、最高价、最低价、收盘价),我们需要在同一处查看它们,比如说在智能系统选项卡中。之后,我们可以在这个简单的示例中开发更多内容,以便实现更高级的软件。
首先,我们需要按照以下步骤声明类:
- 我们需要在全局范围内以 class 关键字声明一个 price 类,并在其内包含所有公开的共用成员。
- 使用 public 关键字。
- 创建五个变量(时间帧、开盘价、最高价、最低价、和收盘价)。
- 创建一个 void 函数来打印所有价格数据。
class CPrices { public: string timeFrame; double open; double high; double low; double close; void pricesPrint() { Print(timeFrame," Prices = Open: ",open," - ","High: ",high,"-","Low: ",low,"-","Close: ",close); } };
从类中创建日线、周线、和月线价格的对象
CPrices CDailyPrices; CPrices CWeeklyPrices; CPrices CMonthlyPrices;
在 OnInit 函数中,我们将为三个时间帧定义以下内容:
- 定义字符串时间帧。
- 调用 iOpen 函数定义开盘价。
- 调用 iHigh 函数定义最高价。
- 调用 iLow 函数定义最低价。
- 调用 iClose 函数定义收盘价。
- 调用打印函数或方法。
int OnInit() { //--- Daily time frame CDailyPrices.timeFrame="Daily"; CDailyPrices.open=(iOpen(Symbol(),PERIOD_D1,1)); CDailyPrices.high=(iHigh(Symbol(),PERIOD_D1,1)); CDailyPrices.low=(iLow(Symbol(),PERIOD_D1,1)); CDailyPrices.close=(iClose(Symbol(),PERIOD_D1,1)); CDailyPrices.pricesPrint(); //--- Weekly time frame CWeeklyPrices.timeFrame="Weekly"; CWeeklyPrices.open=(iOpen(Symbol(),PERIOD_W1,1)); CWeeklyPrices.high=(iHigh(Symbol(),PERIOD_W1,1)); CWeeklyPrices.low=(iLow(Symbol(),PERIOD_W1,1)); CWeeklyPrices.close=(iClose(Symbol(),PERIOD_W1,1)); CWeeklyPrices.pricesPrint(); //--- Monthly time frame CMonthlyPrices.timeFrame="Monthly"; CMonthlyPrices.open=(iOpen(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.high=(iHigh(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.low=(iLow(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.close=(iClose(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.pricesPrint(); return(INIT_SUCCEEDED); }
之后,我们可以在工具箱中的 “Experts” 选项卡中找到执行该智能系统后打印的价格,如下所示:
正如我们在上图中所见,我们得到三行打印:
- 第一行打印开盘价、最高价、最低价和收盘价的日线价格。
- 第二行打印相同的价格,但属于周线数据。
- 第三行也打印相同的内容,但属于月线数据。
indicatorClass 应用程序:
我们需要利用 OOP 方式创建能够打印四种移动平均线类型(简单、指数、平滑、和线性加权平均)数值的软件,以下是创建该类软件的简单步骤:
使用 class 关键字声明指标 CiMA 类,并创建该类的四个常用变量公开成员,它们是 MAType 定义移动平均线类型、MAArray 定义移动平均线数组、MAHandle 定义每种类型的句柄、MAValue 定义每种移动平均线值。创建一个 void 方法或 valuePrint 函数,以及函数的主体,来打印每种移动平均线类型的数值。
class CiMA { public: string MAType; double MAArray[]; int MAHandle; double MAValue; void valuePrint() { Print(MAType," Current Value: ",MAValue); }; };
据该类为每种移动平均线创建以下对象:
- 平均值的名称
- 平均线的句柄
- 平均数组
//--- SMA CiMA CSma; CiMA CSmaHandle; CiMA CSmaArray; //--- EMA CiMA CEma; CiMA CEmaHandle; CiMA CEmaArray; //--- SMMA CiMA CSmma; CiMA CSmmaHandle; CiMA CSmmaArray; //--- LWMA CiMA CLwma; CiMA CLwmaHandle; CiMA CLwmaArray;
在 OnInit 函数中,我们将针对每种移动平均线类型执行以下步骤:
- 定义平均值的名称。
- 定义平均值的句柄。
- 调用 ArraySetAsSeries 设置数组顺序标志为 AS_SERIES。
- 调用 CopyBuffer 函数获取平均指标的缓冲区数据。
- 定义平均值,并调用 NormalizeDouble 函数对其进行常规化。
- 调用创建的打印方法或函数
int OnInit() { //--- SMA CSma.MAType="Simple MA"; CSmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_SMA,PRICE_CLOSE); ArraySetAsSeries(CSmaArray.MAArray,true); CopyBuffer(CSmaHandle.MAHandle,0,0,3,CSmaArray.MAArray); CSma.MAValue=NormalizeDouble(CSmaArray.MAArray[1],_Digits); CSma.valuePrint(); //--- EMA CEma.MAType="Exponential MA"; CEmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_EMA,PRICE_CLOSE); ArraySetAsSeries(CEmaArray.MAArray,true); CopyBuffer(CEmaHandle.MAHandle,0,0,3,CEmaArray.MAArray); CEma.MAValue=NormalizeDouble(CEmaArray.MAArray[1],_Digits); CEma.valuePrint(); //--- SMMA CSmma.MAType="Smoothed MA"; CSmmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_SMMA,PRICE_CLOSE); ArraySetAsSeries(CSmmaArray.MAArray,true); CopyBuffer(CSmmaHandle.MAHandle,0,0,3,CSmmaArray.MAArray); CSmma.MAValue=NormalizeDouble(CSmmaArray.MAArray[1],_Digits); CSmma.valuePrint(); //--- LWMA CLwma.MAType="Linear-weighted MA"; CLwmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_LWMA,PRICE_CLOSE); ArraySetAsSeries(CLwmaArray.MAArray,true); CopyBuffer(CLwmaHandle.MAHandle,0,0,3,CLwmaArray.MAArray); CLwma.MAValue=NormalizeDouble(CLwmaArray.MAArray[1],_Digits); CLwma.valuePrint(); return(INIT_SUCCEEDED); }
编译并执行该段代码后,我们可以找到打印出的每种类型的移动平均线,共四行,如下所示:
正如我之前提到的,我们可以开发这些应用程序来实现更复杂和更高级的任务,但此处标的是学习和理解面向对象编程(OOP)的基础知识,以及如何以 MQL5 应用这种方式,因为当应用这种实用方式时,我们可以获得许多功能。
结束语
在本文中,我们大致学习了编程时一种非常重要的主题和方式的基础知识。通过学习如何帮助我们创建稳妥且高度安全的软件,我们了解到这种方式在设计软件时极其实用,通过将代码划分为小片代码模块来帮助降低创建软件的复杂性,从而令编码过程更容易,还有助于据同一个类创建许多实例,即使具有不同的行为,也不会发生任何冲突,所有这些都为更新软件时的安全性提供了更大的灵活性。
我们还学习了如何在 MQL5 中应用这一重要方式,进而获得所有这些令人难以置信的功能,之后学习了一些简单的应用程序,能够在 MetaTrader 5 中以 MQL5 运用 OOP 方式创建。
我希望这篇文章对您有所启迪,并帮助您学习 MQL5 编程中一个非常重要和关键的主题,也许它会令您更深入地见识到在运用 OOP 方式编码时,多么容易激发您的思路。
如果您喜欢这篇文章,并且想了解更多关于如何基于 MetaTrader 5 中最流行的技术指标,以 MQL5 创建交易系统,以及如何创建自定义技术指标,并在您的智能系统中使用它,您可以通过我的个人资料上的“出版物”板块去阅读我关于这些主题的其它文章,我希望您也发现它们的实用之处。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/12813
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



让我们从 OOP 的定义开始。通过应用 DRY(不重复)概念,OOP 可以帮助您创建和开发可重复使用的软件,而无需重复工作和代码。
这是有道理的,但 OOP 的定义在哪里呢?
封装是一种允许...这很难找到,OOPer的可见性概念显然是private))))而这个访问修饰符 就是封装,读者应该自己去猜))))。
从教科书中摘录 200 tugriks 是很正常的事情,我希望你是自己写的,而不是 GPT))))。
摘自下文:
"在OnInit 函数 中,为三个时间框架定义如下内容:
文章质量取决于翻译质量
马哈茂德正在努力,而你却扑向了他。)
从教科书中摘录 200 句话很正常。我希望你能自己写,不要用 GPT))))。
.