English Русский Español Deutsch 日本語 Português
preview
从基础到中级:定义(二)

从基础到中级:定义(二)

MetaTrader 5示例 |
422 0
CODE X
CODE X

概述

此处提供的材料仅用于教育目的。它不应以任何方式被视为最终应用程序。其目的不是探索所提出的概念。

在上一篇文章“从基础到中级:定义(一)”中,我们讨论了 #define 编译指令。我们看到了如何使用这个指令来简化、加速和使我们的代码更容易实现,以及如何在语言学习阶段创造性地有效地使用它,使一切变得更容易。为了利用这一资源,我们必须了解我们在做什么。总的来说,我们可以为我们的 MQL5 代码提供更加奇特的外观。好的,

因此,有第二种使用 #define 编译指令的方法。为了不使上一篇文章中讨论的内容复杂化,我们决定在单独的文章中展示这一点。这样,我们就可以更加冷静地处理这个问题。你将能够以一种更愉快的形式学习和练习,这将真正使理解和掌握这些概念变得更加容易,因为这些知识对于我们在以下文章中看到的内容非常重要,主要是当我们开始在编程级别工作时,我已经将其视为中级水平。


什么是宏?

简单地说,宏是一个可以在代码中多次使用的小过程。这是对事物的一种非常简单的看法。事实上,大多数情况下(这也延伸到其他更复杂的情况),当代码的一部分几乎不断重复时,我们会创建一个宏。因此,我们不再一遍又一遍地编写相同的代码,而是将其全部放入一个称为宏的块或过程中。

然而,这种定义宏的方法并不真正合适。这是由于几个因素使创建这样的定义变得更加困难。

问题是宏在很大程度上(如果不是几乎总是)是以这样的方式定义的,即代码是内联的,而不是在过程中,这将导致调用在内存中堆叠和去堆叠元素。在我看来,这是对宏的最佳定义。但是,说我们使用宏将代码内联并不意味着让它变得特别。这是因为理论上我们可以用任何过程或函数来实现这一点。我说“理论上”是因为我没有注意到声明为内联或非内联的函数或过程的执行时间有很大差异。

您可能不知道我们在谈论什么,让我稍微澄清一下情况。与 C 和 C++ 类似,MQL5 中有一个保留字,我很少在其他程序员那里注意到,至少在这里。这个保留字是 inline。但这个词在实践中意味着什么?通常,当程序员创建代码时,如果语言允许,他可以将过程调用甚至函数调用转换为内联代码。换句话说,我们停止调用过程,而是接收呈指数级增长的代码,以便运行得更快。而这意味着使用更多的内存。

这可能看起来很愚蠢,甚至很疯狂。然而,如果使用得当,迫使编译器以牺牲更多内存为代价创建更快的代码可能是一个很好的解决方案。然而,我们在这样做时必须小心,因为如果代码呈指数级增长,我们迟早会到达死胡同,因为我们需要越来越多的内存,而处理速度的性能并不能弥补增加内存的成本。

作为示例,让我们看看如何做到这一点。为此,请查看下面的代码。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     Print("Result: ", Fibonacci_Interactive());
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. inline uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default)
21. {
22.     uint v, i, c;
23. 
24.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
25.         v += i;
26. 
27.     return (c == arg ? i : v);
28. }
29. //+------------------------------------------------------------------+

代码 01

请注意解释,因为当我们使用 #define 编译指令创建将在代码中使用的例程或过程时,正确理解宏是什么非常重要。

请注意,代码 01 与我们在其他文章中看到的非常相似。然而,第 20 行有些不同。这个区别正是保留字 inline。现在我们知道了代码 01 是如何运行的,我们可以问:“但是,第 20 行中添加的那个简单单词是怎么让代码与众不同的呢?”

答案是,生成的代码不会像您预期的那样。这是因为相同的代码,如果翻译成 C 或 C++,将会生成略有不同的代码。这不是关于我们看到了什么,而是编译器将如何处理这种编写代码的方式。

同样,我假设 MQL5 编译器的工作方式是,在 01 中编写的代码与之前文章中看到的其他代码之间没有明显区别。说实话,在过程或函数声明中使用或不使用 inline 时,我没有注意到处理速度有任何差异。

无论如何,当编译器看到第 20 行时,它就会明白,每次这个 Fibonacci_Interactive 函数出现在代码中时,第 20 行到第 28 行之间的所有代码都必须替换作为 Fibonacci_Interactive 存在的调用。然而,有必要创建一个包含过程或函数中存在的局部变量的数据库,这样它们就不会与位于代码添加位置的可能局部变量冲突。

为了澄清起见,编译器会将相同的 01 代码编译为如下所示的内容:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     
10.     {
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;
12. 
13.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
14.             v += i;
15. 
16.         Print("Result: ",  (c == arg ? i : v));
17. 
18.     }
19. }
20. //+------------------------------------------------------------------+
21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
22. {
23.     if (arg <= 1)
24.         return arg;
25. 
26.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
27. }
28. //+------------------------------------------------------------------+

代码 02

然而,代码 02 实际上是将要创建和执行的代码。但请注意,对代码的更改必须以非常具体的方式进行。而这将由编译器完成。作为程序员,我们只告诉编译器是否应该将函数或过程内联。但微调将是编译器的责任。

好吧,现在你可能会想,“还好,没有理由担心,因为一切似乎都是一样的。”确实如此,我亲爱的读者。这是一个简单的例子,但请记住,代码 01 第 20 行中的函数将在我们的代码中使用一千次。编译器将执行代码 02 中所示的操作一千次。结果,可执行文件会变得越来越大,占用越来越多的空间,并且需要越来越多的时间来加载。即使执行速度再快,也无法弥补这一点。

因此,如果你理解了这一点,那么理解宏是什么(我们稍后将对其进行定义)将非常简单。我们甚至可以使用此代码定义第一个宏。请注意,在第 10 行和第 18 行之间,我们有一段完整的代码,或者更确切地说是一个块,与其余代码隔离。要将此代码转换为我们的第一个宏,我们只需将其更改为如下所示的内容:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09. 
10. #define macro_Fibonacci_Interactive     {                           \
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
12.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
13.         Print("Result: ",  (c == arg ? i : v));                     \
14.                                         }
15. }
16. //+------------------------------------------------------------------+
17. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
18. {
19.     if (arg <= 1)
20.         return arg;
21. 
22.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
23. }
24. //+------------------------------------------------------------------+

代码 03

现在最有趣的部分是:老实说,代码 03 几乎与代码 02 相同。但执行时,结果会和代码 02 不一样。亲爱的读者,你明白其中的区别吗?很有可能,你不知道。这是因为当你第一次熟悉宏观时,它并不那么明显。

现在请注意以下几点:代码 02 和 03 之间所做的不同就是在第 10 行添加了 #define 编译指令。其后跟一个常数。是的,宏是一个常数,永远不要忘记它。

定义宏时,宏必须包含在一行中。

不可能创建包含多行代码的宏。该规则由编程语言决定。因此,为了从多行创建宏,必须在每行末尾添加一个特殊字符。从第 10 行开始,每一行都可以看到这个字符,但要小心,不要将此行放在最后一行。否则,宏将不会在我们期望的位置,您在尝试编译代码时可能会收到错误警告。

一切看上去都很简单,不是吗?多少是这样的。如果细心的话,是的,创建宏非常简单。但如果你认为代码 03 中只会看到这些,那你就错了。这将是一个基于代码 02 的宏。请注意,执行代码 03 时结果将与代码 02 不同。但是为什么呢?原因是该宏未使用。我们只是对此进行声明。因此,在代码 02 的输出中,如下图所示,我们将有两条消息。

图 01

然而,当执行代码 03 时,输出将如下所示:

图 02

那么,这个问题该如何解决呢?为此,我们必须指示编译器在代码中使用宏。正如你在下面看到的,这很简单。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09. 
10. #define macro_Fibonacci_Interactive     {                           \
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
12.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
13.         Print("Result: ",  (c == arg ? i : v));                     \
14.                                         }
15. 
16.     macro_Fibonacci_Interactive;
17. }
18. //+------------------------------------------------------------------+
19. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
20. {
21.     if (arg <= 1)
22.         return arg;
23. 
24.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
25. }
26. //+------------------------------------------------------------------+

代码 04

现在,这是正确的。当在代码 04 中包含第 16 行时,您将获得与图 01 相同的结果,表明代码按预期运行。然而,那是最简单的部分,现在我们来到最有趣的部分:让我们重点讨论一下这个单独的主题。这是必要的,因为我们必须首先了解到目前为止已经展示了什么,以便了解接下来要做什么。


向宏传递参数

“我的天啊!我以为我们已经完成了,而你却在这里宣布可以将参数传递给宏。”现在让我们检查一下我是否正确理解了宏的概念。如果我错了请纠正我。

宏是存在于例程中的代码,我们希望将其内联到代码中,对吗?正是这样。你是对的。因此,如果我们可以创建一种在宏中实现整个函数或过程的方法,我们就不必创建相同的函数或过程,因为编译器在编译代码时并不重要。我说得对吗?还是对的。然而,只有一个小细节:过程比函数更容易实现,因为在大多数情况下函数需要一个额外的变量。不管怎样,这是正确的。

“那我明白了。如果我想将参数传递给宏,我应该使用与声明函数或过程时相同的声明方法。这很简单,对我来说很明显。”

嗯,从这个意义上来说你几乎是对的。这是因为我们没有像使用函数或过程时那样在宏中声明传递的参数。在这种情况下,事情的运作方式有点不同,这就是困难所在。与在函数和过程中传递参数不同,在函数和程序中,我们可以说“此参数无法控制”、“此参数可以是”、“该参数属于某某类型”和“该参数为某某类型”,在宏中,我们没有那种控制。因此,为了避免一些相当严重的错误,我们没有得到编译器的帮助。

这就是区别所在。因此,许多程序员避免使用宏。这是因为代码中的任何粗心都可能导致严重的问题,因为宏中的错误很难精确修复,因为它们很难找到。关键在于,在代码的一部分中,宏可以产生正确的值,但在同一代码的另一部分,更进一步,同一宏可以产生不正确的值。在我看来,能够检测和修复这种类型的故障是最困难和最艰巨的任务之一。

因此,在代码中使用宏时要非常小心:它们是一种宝贵的资源,但它们会让你浪费时间来解决一个本来很容易解决的问题。

因此,现在让我们执行以下操作:由于代码 04 只是代码 01 的修改,在我们可以使用宏的范围内,我们可以修改代码 04 以了解如何在宏内传递值。

请注意以下内容:在代码 01 中的第 8 行和第 9 行,我们可以选择使用两个不同的函数:一个函数的答案是递归的,另一个函数的答案是交互式的。但是在代码 04 中,即使我们有两个响应,我们也不能将值传递给交互式计算部分,除非我们按照第 4 行定义中指定的方式更改该值。但事实并非如此,因为我们希望能够传递任何值,就像代码 01 的情况一样。

为了更容易理解上述内容,让我们看看下面的代码。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive     {                           \
07.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         Print("Result: ",  (c == arg ? i : v));                     \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     Print("Result: ", Fibonacci_Recursive(15));
15.     macro_Fibonacci_Interactive;
16. }
17. //+------------------------------------------------------------------+
18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
19. {
20.     if (arg <= 1)
21.         return arg;
22. 
23.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
24. }
25. //+------------------------------------------------------------------+

代码 05

当我们运行代码 05 时,我们将看到下图所示的结果。

图 03

显然,结果是不同的。发生这种情况是因为在代码 05 的第 14 行中,我们在第 18 行将一个值作为函数参数传递。在宏中,情况并非如此,因为该值在第 4 行的定义中是固定的。因此,正如您所看到的,使用宏需要一些谨慎和更多的关注。为了解决这个问题,应该向宏传递一些参数。为此,我们将稍微修改宏代码。在下面的代码中可以看到这个修改:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive(arg) {                          \
07.         uint v, i, c;                                               \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         Print("Result: ",  (c == arg ? i : v));                     \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     Print("Result: ", Fibonacci_Recursive(15));
15.     macro_Fibonacci_Interactive(14);
16. }
17. //+------------------------------------------------------------------+
18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
19. {
20.     if (arg <= 1)
21.         return arg;
22. 
23.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
24. }
25. //+------------------------------------------------------------------+

代码 06

执行代码 06 后,结果将如下所示:

图 04

很明显,这些值是不同的,但我们这样做是为了表明我们可以传递独立的值,并且一个值与另一个值无关。这清楚地表明,宏与任何其他函数或过程无关。

这很有趣,但并不令人兴奋,因为宏既不像代码 01 中的函数,也不像单独的过程。实际上,它只是孤立的代码。然而,代码 06 仍然像代码 02 一样运行,在大多数情况下,这是完全无用的,因为它除了将代码 06 的第 15 行放在其他各个点之外,没有任何其他用途。但是,由于这些代码具有教学目的,因此它们很简单,不需要使用宏等工具。然而,我们在这里这样做正是为了解释如何使用它和使用它。

让我们考虑一下如何使宏像函数一样运行。事实上,我们不能将代码 06 中的宏用作函数。如果你还记得,函数就像一个特殊的变量:当我们使用它根据传递给它的参数检索值时,它总是返回一个值。请注意我们所说的。我并不是说我们不能将宏用作函数。我的意思是我们不能像使用函数一样使用这个宏。注意不要混淆绵羊和山羊。

然而,尽管存在这个初始限制,我们可以使这个宏表现得像一个通过引用进行步骤的过程。如何做呢?很简单:修改代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive(arg) {                          \
07.         uint v, i, c;                                               \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         arg = (c == arg ? i : v);                                   \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     ulong value;
15. 
16.     Print("Result: ", Fibonacci_Recursive((uint)(value = 15)));
17.     macro_Fibonacci_Interactive(value);
18.     Print("Checking: ", value);
19. }
20. //+------------------------------------------------------------------+
21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
22. {
23.     if (arg <= 1)
24.         return arg;
25. 
26.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
27. }
28. //+------------------------------------------------------------------+

代码 07

现在事情变得有趣了,事实上,我们开始使用宏,为了更高的目的。请注意以下几点:在第 14 行我们声明了一个变量,但我们没有对其进行初始化。我们在第 16 行对其进行初始化。您可能会问,“第 16 行到底发生了什么?”别担心,这并不疯狂。

请注意,第 14 行中的变量是 ulong 类型,而第 21 行中的函数期望的类型是 uint 类型。由于我们想对第 21 行中的函数和下面将使用的宏使用相同的值,因此我们应该进行显式的类型转换,以防止编译器发出警告。因此,第 16 行的声明如下所示。

好的,紧接着我们有第 17 行。乐趣就从这里开始,发生这种情况是因为我们通过引用传递变量。因此,当宏更改变量的值时,这种更改将在我们的主代码中反映出来。该值在第 9 行发生变化,因此应保持警惕。否则,我们可能会得到一颗定时炸弹。为了证明该系统的工作原理和可用性,我们有第 18 行,其中变量的值被输出到终端。执行代码 07 后,我们将得到如下所示的图像:

图 05

好吧,我相信很明显,每次我们向宏传递变量时,我们都是通过单击链接来实现的。这就是为什么我们必须小心,不要让宏错误地改变值。但是等一下,我们在代码 07 中看到的宏类型是作为过程运行的宏。是否有方法将宏用作函数?也就是说,我们可以发送一个值并接收另一个值作为响应。许多程序员在学习宏的一开始就有这个问题。

基本上,宏更面向过程,但取决于我们如何在宏中构建代码,我们可以让它像函数一样运行。在这种情况下,我们处理这些文章中提供的代码,我们可以创建一个这样的示例,作为表示机制的一种模式。尽管它没有经过深思熟虑,看起来也不太有趣,但值得一试。下面的代码中可以看到一个例子:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Ternário(A, B, C, D)  (A == B ? C : D)
07. //+----------------+
08. #define macro_Fibonacci_Interactive(arg) {                          \
09.         uint v, i, c;                                               \
10.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
11.         arg = macro_Ternário(c, arg, i, v);                         \
12.                                         }
13. //+----------------+
14. void OnStart(void)
15. {
16.     ulong value;
17. 
18.     Print("Result: ", Fibonacci_Recursive((uint)(value = 15)));
19.     macro_Fibonacci_Interactive(value);
20.     Print("Checking: ", value);
21. }
22. //+------------------------------------------------------------------+
23. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
24. {
25.     if (arg <= 1)
26.         return arg;
27. 
28.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
29. }
30. //+------------------------------------------------------------------+

代码 08

这里,在代码 08 中,我们可以看到一个作为函数运行的宏的小演示。这个宏在第 6 行定义,是宏如何作为函数运行的一个简单示例。请注意,我们在第 11 行使用了它。本质上,这允许您隐藏代码中可能存在的一些复杂性,因为根据我们给宏的名称,更容易理解它的工作原理。

请注意,这里的目标不是使代码更高效,而是使其更具可读性。例如,您可以创建一组宏来操纵日期和时间值,以使用 datetime 类型。这是具有函数功能的宏的典型示例。为了使这更直观,让我们创建一些宏来演示我们正在谈论的内容。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. enum eConvert {
05.     FORMAT_DECIMAL,
06.     FORMAT_OCTAL,
07.     FORMAT_HEX,
08.     FORMAT_BINARY
09. };
10. //+------------------------------------------------------------------+
11. #define macro_GetDate(A)            (A - (A % 86400))
12. #define macro_GetTime(A)            (A % 86400)
13. //+----------------+
14. #define macro_GetSec(A)             (A - (A - (A % 60)))
15. #define macro_GetMin(A)             (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
16. #define macro_GetHour(A)            (int)((A - (A - ((A % 86400) - (A % 3600)))) / 3600)
17. //+----------------+
18. #define PrintX(X)                   Print(#X, " => ", X);
19. //+------------------------------------------------------------------+
                   .
                   .
                   .

片段 01

在代码片段 01 中,我们拥有将作为包含文件的内容。我加入宏是为了表明,随着时间的推移,随着经验的积累和特定库的构建,我们可以大大扩展我们的编程能力。

为了测试这些宏在第 11 行到第 18 行之间执行的操作,让我们使用一些测试代码。请在下面找到它:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     datetime dt = D'2024.08.15 12:30:27';
09.     ulong v;
10. 
11.     Print("Date And Time: ", dt);
12.     Print("0x", ValueToString(dt, FORMAT_HEX));
13.     PrintX((datetime)(v = macro_GetDate(dt)));
14.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
15.     PrintX((datetime)(v = macro_GetTime(dt)));
16.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
17.     PrintX((datetime)(v = macro_GetSec(dt)));
18.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
19.     PrintX((datetime)(v = macro_GetMin(dt)));
20.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
21.     PrintX((datetime)(v = macro_GetHour(dt)));
22.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
23. }
24. //+------------------------------------------------------------------+

代码 09

这非常有趣和令人兴奋,因为如果不付出太多努力,我们可以分析一些事情,否则这些事情必须在尚未解释的其他资源的帮助下完成。当我们运行代码 09 时,它将显示以下内容:

图 06

现在你会明白为什么我说代码 09 很有趣。在第 13、15、17、19 和 21 行,我们调用了宏。该宏在代码片段 01 的第 18 行定义。宏的目的是告诉我们变量的名称及其值。在这种情况下,变量名是一个在代码 09 中提到的每一行中使用不同宏的函数。请注意,这是一个相当不寻常和有趣的组合,因为每个宏返回的值都被放置在一个局部变量中,然后立即打印该变量的十六进制值及其十进制值。

这表明我们确实以 datetime 格式获取了正确的值。我知道你们中的许多人可能会认为这是无稽之谈,但正如你们所看到的,这个宏非常有用,尤其是当我们希望代码尽可能快速安全地运行时。它使编写代码的整个工作变得更加容易、愉快和有趣。


最后的探讨

本文考虑了它是什么以及如何处理用于编码的宏,因为它们是经常被误用的资源之一,在其他情况下完全被忽视,正是由于缺乏使用它们的知识和实践。许多程序员最终不明白为什么他们认为不可能(甚至无法实现)的事情可以由另一个通常不为人知的程序员完成。

我自己坚持(我喜欢这样认为)以下论点:

没有糟糕的程序员,只有那些还没有找到自己的方向的程序员。是的,既有未经训练的专业人士,也有认为自己已经做好准备的专业人士。但事实上,他们对编程仍然是新手。

我在这些文章中试图向您传达的这个概念是专门为那些刚刚开始学习编程的人设计的。如果你从正确的道路开始,正确理解特定工具存在的概念和原因,你将更容易将你的想法变为现实。所以,亲爱的读者,你可能会觉得这些材料很愚蠢,没有具体的目的或任务,但如果你意识到并在实践中应用这里讨论的内容,你会发现许多以前对你来说看起来困难和艰难的事情都会变得相当可以实现。当然,还有一些事情需要澄清,但我们已经朝着正确的方向迈出了许多步。

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15588

附加的文件 |
Anexo.zip (4.58 KB)
逆公允价值缺口(IFVG)交易策略 逆公允价值缺口(IFVG)交易策略
当价格回到先前确定的公允价值缺口位置,且未表现出预期的支撑或阻力反应,而是无视该缺口时,便出现了逆公允价值缺口(IFVG)。这种“无视”现象可能预示着市场方向的潜在转变,并为反向交易提供优势。在本文中,我将介绍自己开发的量化方法,以及如何将IFVG作为一种策略,应用于MetaTrader 5智能交易系统(EA)中。
精通日志记录(第四部分):将日志保存到文件 精通日志记录(第四部分):将日志保存到文件
在本文中,我将向您讲解基本的文件操作,以及如何配置一个灵活的自定义处理器。我们将更新 CLogifyHandlerFile 类,以将日志直接写入文件。我们将通过在 EURUSD 上模拟一周的策略来进行性能测试,在每个 tick 生成日志,总耗时为 5 分 11 秒。其结果将在未来的文章中进行比较,届时我们将实现一个缓存系统来提升性能。
3D 柱线上的趋势强度和方向指标 3D 柱线上的趋势强度和方向指标
我们将研究一种市场趋势分析新方法,基于市场微观结构的三维可视化、及张量分析。
从基础到中级:定义(一) 从基础到中级:定义(一)
在这篇文章中,我们将做一些许多人会觉得奇怪和完全脱离上下文的事情,但如果使用得当,这将使你的学习更加有趣:我们将能够根据这里显示的内容构建非常有趣的东西。这将使您更好地理解 MQL5 语言的语法。此处提供的材料仅用于教育目的。它不应以任何方式被视为最终应用程序。其目的不是探索所提出的概念。