语句、代码块和函数
因此,在向导生成的脚本中,OnStart 函数如下所示:
void OnStart()
|
这正是我们在 MQL5 编程环境中的第一个主题。此时,我们再次遇到未知的概念和字符序列。为了进行解释,我们先要补充点其他知识。
程序在运行时通常必须实现以下几个典型阶段:
- 定义变量,即计算机存储器中的命名存储单元,用以存储数据;
- 组织源数据输入;
- 处理数据,应用的某种算法;以及
- 组织结果输出。
就维护程序的语法正确性而言,并非所有这些阶段都是必需的。例如,如果我们创建一个计算 2*2 乘积的程序,显然不需要任何输入数据,因为乘法所需的数字已整合在程序文本中。此外,由于 2 和 2 在该表达式中是常数值,所以在程序中不需要命名存储单元(变量)。由于我们已经知道了二乘二是多少,就不需要传达乘积数了。当然,这样的程序没有任何实际功能。但从技术角度来看,又是绝对正确的。
更有趣的是,程序可能不包含任何处理语句。我们的脚本模板专门提供一个示例程序 'null' 。那么上面的文本片段是什么?
在 Niklaus Wirth(编程界的一位大腕)的时代,他给编程下了一个简单的概括性定义,即编程是算法和数据结构的共生关系。
“算法”是指特定编程语言的语句序列。语句也是一种句子,可以看做是根据其语法规则用编程语言表达的完整话语。“语句”这个名字本身就表明它被计算机视为操作指南。换句话说,语句描述了何时以及何种方式处理所需的应用数据结构。这恰恰解释了为何算法和数据结构的相互渗透能将作者的想法付诸实践。
遗憾的是,在大多数实际任务中,语句数量过于庞大,以至于必须在一定程度上系统化,以便人类识别和控制程序行为。
此时,分治算法也派上了用场,这种算法在编程中几乎无处不在,只是以不同形式出现。在本书的后续学习中,我们将继续学习这些概念,现在我们仅关注其本质。
我们知道,该算法简化为将一个庞大的复杂任务分解成多个规模缩小且难度降低的子任务。我们可以将这个过程比作建造房子或组装航天器。这两种“产品”都是由多个不同的模块组成,这些模块又由组件组成,而组件又由更小的零件组成,以此类推。
将这种相似性延伸到算法领域,我们可以说语句是很小的零件,而整个程序就是一个房子/航天器。因此,我们需要大小适中的结构块。
这就是为什么在实现算法时,习惯于将逻辑上相关的语句组合成更大的已命名片段,即函数。在程序需要时,我们可以通过函数名来寻址函数(即调用函数),从而要求计算机执行该函数中包含的所有语句。事实上,整个程序是最大的外部块,因此,它也可以由函数来表示,从函数中调用更小的函数,或者如果语句不多,就立即执行语句。现在我们来分析 OnStart 函数。
在脚本中,OnStart 是保留名称,表示当用户使用上下文菜单命令或通过在图表上拖动鼠标启动脚本时,终端本身调用的最终函数,以此作为对用户操作的响应。因此,代码前面的片段定义了 OnStart 函数,此函数预先决定了整个脚本的行为。
如果懂其他编程语言,比如 C、C++、Rust,或者 Kotlin,就会注意到该函数和 main 函数很相似,都是进入程序的核心入口。
任何脚本都必须包含 OnStart 函数。否则,编译结束时会报错。
脚本以任何方式启动后,终端首先会执行空函数 OnStart(如上述示例中所示),并立即完成其操作。严格来说,我们的脚本中还没有应用任何算法,但是已存在一个存根函数用于添加算法。
在其他类型的 MQL 程序中,还有一些特殊函数需要由程序员在其代码中定义。我们将在本书的相关章节讨论具体特性。
我们将在本书的第二章详细讨论函数定义语法。要通过动手实践来回顾,考虑以下基本要点就足以理解对 OnStart 的说明。
由于函数通常旨在获得相应的结果,因此期望值的特征在其定义中以特殊的方式描述:应获得哪些数据类型,以及这些数据是否必要。有些函数可以执行不需要返回值的操作。例如,某函数可用于更改当前图表的设置,或者在账户达到预定义的提款级别时发送推送通知。所有这些操作都可以通过函数中的语句进行编程,并且不会创建任何新数据(自然也无需返回给程序的任何其他部分)。
我们的情况与此类似:作为脚本的主函数,OnStart 可以在完成后仅将其结果返回给外部环境(直接返回给终端),但这不会脚本操作本身产生任何影响(因为脚本已经执行完毕)。
正因如此,OnStart 函数名前面有 void 一词,就是为了通知编译器该结果对我们不重要(void 意思是空)。void 是 MQL5 中的众多过程保留字之一。编译器知道所有保留字的含义,并且在审查源代码时以它们为指导。特别是,程序员可能使用保留字来为编译器定义新术语,例如 OnStart 函数本身。
名称后的圆括号对于任何函数说明都是必需的:圆括号中可以包含函数参数列表。例如,我们要编写一个函数,用于求一个数字的平方,则必须为该函数提供一个参数来表示这个数字。然后,我们可以从程序的任何部分调用该函数,并向其传送一个自变量,即该参数的具体值。后面我们会学习如何描述参数列表;本例中并未出现。此要求是针对 OnStart 函数提出的,因为它是由终端本身调用的,并且不会将任何内容作为参数发送至该函数。
最后,花括号用于标记包含语句的代码块的开始和结束。此类代码块紧跟在函数名字符串之后,包含由该函数执行的一组运算。也被称为函数体。在本例中,花括号内没有任何内容。因此,脚本模板不会执行任何操作。
上述由关键字 void、名称 OnStart、空参数列表和空代码块的序列,为编译器定义了 OnStart 函数的最少空实现。稍后我们会在函数体中添加语句,从而扩展 OnStart 函数的定义。
执行编译命令后,我们将确保脚本可以成功编译,并且就绪程序出现在文件夹 Scripts/MQL5Book/p1 终端的导航器中。这是因为在磁盘上的相关文件夹中,现在有了 Hello.ex5 文件。可以在任何文件管理器中轻松验证这一点。
我们可以对图表运行脚本,但要确认脚本是否已经执行,唯一证据是终端日志中的条目(工具窗口中的日志选项卡;请勿与工具栏混淆):
Scripts script Hello (EURUSD,H1) loaded successfully
|
即脚本被加载,控件被发送给 OnStart 函数,但因为函数没有处理而立即被返回至终端,之后终端从图表中卸载该脚本。