从基础到中级:定义(一)
概述
此处提供的材料仅用于教育目的。它不应以任何方式被视为最终应用程序。其目的不是探索所提出的概念。
在上一篇文章“从基础到中级:递归”中,我们介绍了递归是什么以及如何在各种场景中将其用作一种非常有用的编程技术。它允许我们以简单易行的方式创建机制和实现。尽管如此,我们必须考虑到代码有时会运行得更慢的可能性,因此在这种情况下应该表现出耐心。
但总的来说,递归对我们有帮助,让我们的生活变得容易得多。因此,对于那些想成为经验丰富的专家(或者至少对编程相关的事情有很好的理解)的人来说,这应该是一个学习的主题。
本文考虑了之前已经涵盖的主题:定义的使用,包括构建宏、声明以及更精细地控制某些部分,这些部分可能由我们或您感兴趣的其他程序员实现,并且您希望以受控的方式修改这些部分以实现某些目标。
我们将会有什么样的定义?
定义可以在同一代码中采取不同的形式,并根据程序员想要实现的语言和目标,对概念和预期用途进行微小的更改。一般来说,该定义通过创建宏、编译指令或声明特殊常量,帮助我们轻松、快速、安全地控制和更改代码。无论如何,你会喜欢这些定义的,如果你想了解它们是如何运行的,你会非常喜欢它们的,并且会尽可能地使用它们。但最重要的是,如果你想对代码进行细微的更改来测试这些更改的结果,你会喜欢使用定义。这是可能的,因为定义允许以非常简单和实用的方式完成。
此外,我们将研究面向编译指令使用的定义。还有其他类型的定义是在使用外部编程时创建的,以及在导入其他环境中创建的代码以与 MQL5 联合使用时创建的。然而,这些定义要求我们具备嵌入式编程经验,即使我们只使用 MQL5 作为主要语言。
例如,MQL5 允许您导入其他程序员创建的代码和功能,通常是 C 或 C++。这使您可以向 MetaTrader 5 添加功能或个人感兴趣的元素,例如视频播放器,甚至可以开发用于创建高级科学图表的实用程序,并可以选择使用像 LaTeX 这样的语言,这是一种允许您格式化数学表达式的语言,如果您不知道的话。顺便说一句,它非常有趣。
由于我们将重点介绍编译指令作为创建定义的一种方式,因此本主题将更简单、更有趣。我相信你会很快理解的,因为我们在之前的文章中已经以这样或那样的形式介绍过了。因此,我们将从 #define 指令本身开始。
这个编译指令非常具体。这是因为使用它时,我们可以创建:常量或宏。这基本上是我们使用这个指令所能做的一切。与 #include 指令类似,该指令已在另一篇文章中介绍过,即从基础到中级:Include 指令。这个 #define 指令与其他编译指令一起允许我们生成一些小的、易于创建和修改的设置。例如,在上一篇文章中我们看到了下面的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const uchar who = 6; 07. 08. Print("Result: ", Fibonacci_Recursive(who)); 09. Print("Result: ", Fibonacci_Interactive(who)); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. uint Fibonacci_Interactive(uint arg) 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
现在请注意,在第 6 行我们定义了一个 uchar 类型的常量。但请注意,必须接收值的函数期望接收 uint 类型的值,如第 12 行和第 20 行所见。但你不应该不断地适应这一点,而我们可以使用编译指令来使这样的事情变得更好。同时,在某些情况下,它可以使代码更快一些。下面你可以看到为什么使用编译指令而不是常量可以使我们的代码更快,以及其他有助于提高整体性能的东西。
假设我们想在这里使用编译指令,代码 01 可以重写如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive(def_Fibonacci_Element)); 09. Print("Result: ", Fibonacci_Interactive(def_Fibonacci_Element)); 10. } 11. //+------------------------------------------------------------------+ . . .
代码 02
当然,我们不会重复整个代码,因为没有必要这样做。但是,看看我们是如何实现它的。这似乎没有什么区别,但确实有区别,我亲爱的读者。对于那些阅读代码的人来说,它可能看起来是一样的,但对于编译器来说,代码 01 与代码 02 完全不同,因为在代码 02 中我们使用了 #define 指令。
因此编译器实际上会看到如下所示的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print("Result: ", Fibonacci_Recursive(6)); 07. Print("Result: ", Fibonacci_Interactive(6)); 08. } 09. //+------------------------------------------------------------------+ . . .
代码 03
但是等一下:它相当于代码 01。因此,使用此指令没有意义。事实上,一切都非常合乎逻辑。我们只需要记住,我们对这里的教育目的感兴趣。我们的代码中可能有大量位置必须使用特定值。如果我们使用一个常数,即使它是一个全局常数,在某些时候,我们可能会因为这个常数的存在而遇到问题。
然而,如果我们使用代码 02 中所示的定义,我们可以对代码进行更多的控制。此外,除非常量是全局的,否则它将仅在声明它的过程中存储,或者更确切地说是可见的。另一方面,它是一个指令 —— 是的,因为一旦声明了它,只要它存在,我们就可以在代码的任何地方使用它而不会出现任何问题。我这么说是因为,与全局常量不同,指令可以随时被销毁,其值可以更改,甚至可以具有与同名指令完全不同的功能。
现在,让我们逐一讨论,因为这里提到的每一点都很重要,你可能会在某个时候需要它。现在,让我们先看看可见性问题,这是一个非常简单的情况,正如您在下面的代码中看到的那样:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. 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. //+------------------------------------------------------------------+
代码 04
请注意以下几点:在第 4 行我们声明了一个常量类型编译指令。当我们为函数或过程中的参数定义默认值时,它在整个代码体中都是可见的,并且具有相同的类型和预期结果。但是,相同的编译指令可以用全局常量替换。但这就是事情变得有趣的地方,因为我们不能用常量做下面代码显示的事情:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. #undef def_Fibonacci_Element_Default 21. //+------------------------------------------------------------------+ 22. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 23. { 24. uint v, i, c; 25. 26. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 27. v += i; 28. 29. return (c == arg ? i : v); 30. } 31. //+------------------------------------------------------------------+
代码 05
请注意,在这段代码的第 20 行,我们使用了另一个编译指令,即 #undef。当使用该指令时,我们可以删除或销毁使用 #undef 指令中包含的名称定义的指令。这样的功能非常有用。然而,在我们讨论更关键的实用程序之前,我们需要讨论一下当我们尝试编译代码 05 时会发生什么。当尝试编译时,编译器将返回以下错误:

图 01
它表明错误发生在第 22 行。然而,def_Fibonacci_Element_Default 常量在第 20 行被破坏了。因此,当编译器试图在数据库中查找命名常量来编译代码时,它将找不到它。这将导致图 01 中看到的错误。因此,许多程序员习惯于在错误前添加前缀,以帮助识别生成的错误类型。这不是一个规则,而是一个很好的编程实践。例如,我喜欢在任何已定义的常量前加上 def_,这样我就可以区分通用常量和编译指令。
“好的,但是如果我们想在指令被销毁后立即为它声明另一个值,我们可以这样做吗?”当然,我亲爱的读者!但我们必须对此小心。稍后,我将向您展示如何避免这种情况,避免不必要的麻烦。它不是一个 100% 有效的工具,但至少它有帮助。但是,让我们看看如果我们按照提议更改指令会发生什么。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. #undef def_Fibonacci_Element_Default 21. //+----------------+ 22. #define def_Fibonacci_Element_Default 7 23. //+------------------------------------------------------------------+ 24. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 25. { 26. uint v, i, c; 27. 28. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 29. v += i; 30. 31. return (c == arg ? i : v); 32. } 33. //+------------------------------------------------------------------+
代码 06
因此,在代码 06 中我们可以看到该提议是如何实现的。请注意,在第 22 行,我们为指令定义了一个新值,当执行此代码时,结果将如下所示:

图 02
如果你注意到了,结果是不同的,但正是由于值的变化,这是意料之中的。如果第 4 行定义的指令是全局常数,我们将无法进行第 22 行显示的更改,也无法从代码的其他部分删除全局常数。这是你应该努力理解的基础。这不仅仅是你必须记住的东西,而是你必须理解和采用的东西。
好的,我们已经看到 #define 和 #undef 编译指令可以以简单的方式一起使用。因为,正如我最近提到的,有更复杂的方法可以同时使用这两个指令。然而,要做到这一点,我们需要使用其他指令来简化工作并促进控制。
控制代码版本
#define 和 #undef 指令最有趣的用途之一是控制同一代码的版本。由于这段用于计算斐波那契序列中元素的代码非常容易理解,我们将用它来解释版本控制。
假设我们不知道如何为斐波那契数列的给定元素创建迭代计算的版本,或者我们想创建一个与到目前为止代码中显示的计算不同的计算。你可能认为这很容易做到,但迟早你会犯错误。但是,如果使用编译指令,则大大降低了出错的风险。例如,我们可以隔离计算,以选择是迭代计算还是递归计算。为此,只需创建类似于下图所示的内容:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. if (arg <= 1) 18. return arg; 19. 20. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 21. } 22. //+----------------+ 23. #endif 24. //+------------------------------------------------------------------+ 25. #ifdef DEF_INTERACTIVE 26. //+----------------+ 27. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 28. { 29. uint v, i, c; 30. 31. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 32. v += i; 33. 34. return (c == arg ? i : v); 35. } 36. //+----------------+ 37. #endif 38. //+------------------------------------------------------------------+
代码 07
在这里,我们展示了第一种选择,看看我们能做什么。请注意,我们在代码 07 的第 4 行创建了一个定义。它不一定必须包含任何值,它只需要存在,或者更确切地说,编译器必须能够看到它。请注意,在第 10 行,我们只有一个斐波那契函数。但我问你:会用哪一个 —— 第 15 行的还是第 27 行的?这时你可能会说:这毫无意义。可以有两个同名的函数吗?是的,但我们下次再讨论这个问题。现在,让我们关注代码 07 中显示的内容。
如果你从未见过这样的结构,你很难理解这里发生了什么。问题是,乍一看,这没有任何意义。但仔细看看代码,请注意,在第 13 行我们使用了另一个编译指令。这将检查声明的指令是否存在,并且仅当指令不存在时才会编译给定的 #ifndef 和 #endif 指令之间的代码。否则,代码将不会被编译。类似的事情发生在第 25 行,其中代码仅当指令存在时才进行编译。也就是说,由于第 4 行定义了我们要在其中使用代码的指令,因此只会编译第 25 行和第 37 行之间的代码。同时,第 13 行和第 23 行之间的代码被忽略。
是不是这样呢?让我们实际测试一下。为了使其真正被接受,让我们在代码 07 中添加一小行,它看起来会像这样:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. Print("Testing ", __LINE__); 18. if (arg <= 1) 19. return arg; 20. 21. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 22. } 23. //+----------------+ 24. #endif 25. //+------------------------------------------------------------------+ 26. #ifdef DEF_INTERACTIVE 27. //+----------------+ 28. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 29. { 30. uint v, i, c; 31. 32. Print("Testing ", __LINE__); 33. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 34. v += i; 35. 36. return (c == arg ? i : v); 37. } 38. //+----------------+ 39. #endif 40. //+------------------------------------------------------------------+
代码 08
当我们运行代码 08 时,我们会看到以下内容:

图 03
现在让我们将第 4 行更改为如下所示的内容:
// #define DEF_INTERACTIVE 接下来再次编译代码 08,结果如下:

图 04
就是这个!事实证明:它有效。在一个版本中,我们使用递归计算,在另一个版本里,我们使用迭代计算。要选择使用哪个版本,只需更改一行代码即可。在我们的代码中这样实现和使用指令难道不有趣吗?但事情不仅仅如此,我们可以做一些更有趣的事情。请注意,显示的代码可以以更简单的方式还原。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE "Interactive" 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result to ", 11. #ifdef DEF_INTERACTIVE 12. DEF_INTERACTIVE, " : " 13. #else 14. "Recursive : " 15. #endif 16. , Fibonacci()); 17. } 18. //+------------------------------------------------------------------+ 19. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 20. { 21. #ifdef DEF_INTERACTIVE 22. 23. uint v, i, c; 24. 25. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 26. v += i; 27. 28. return (c == arg ? i : v); 29. 30. #else 31. 32. if (arg <= 1) 33. return arg; 34. 35. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 36. 37. #endif 38. } 39. //+------------------------------------------------------------------+
代码 09
在代码 09 中,我们以更有条理的方式使用指令,因为我们只需更改一行代码即可控制多项内容,就像在代码 08 中所做的那样。因此,我们将执行以下操作:首先,我们将运行如上所示的代码,即使用现有的第 4 行。执行结果如下:

图 05
如果以与代码 08 中相同的方式排列第 4 行,则执行代码 09 的结果如下:

图 06
“这不是疯了吗?现在我非常有兴趣了解这一点,因为我不明白代码 09 中发生了什么。那么,你能向我解释一下发生了什么,为什么会得到这样的结果吗?”当然可以,我亲爱的读者。这正是撰写本文的原因。
我们在代码 09 中所做的只是一个小玩笑,表明我们可以用更少的资源做更多的事情。我知道很多人很难理解程序员是如何思考的,但实际上并没有那么难。优秀的程序员总是在寻找节省工作和提高性能的方法。在代码 09 中我们确实做到了这一点,但是采用了一种更具创造性的方式。
我想您已经了解 #ifdef 指令背后的思路,但让我们让它变得更加有趣。我们在另一篇文章中讨论过的 IF 语句在 #ifdef 和 #ifndef 指令中的工作方式完全相同。也就是说,块内的所有内容都将被执行或不执行。然而,在 if 语句中,块由左括号和右括号分隔。这里 #ifdef 或 #ifndef 指令由 #endif 指令关闭。总是要这样。然而,可能发生的情况是,我们正在测试一些东西,不想重复指令语句。在这种情况下,我们可以在 #ifdef 或 #ifndef 指令的构造块内放置 #else 指令。
请注意,就像 if 语句一样,当表达式的计算结果为 false 并且我们可以执行 else 中与 if 语句关联的任何内容时,我们可以在这里使用 #ifdef 和 #ifndef 指令做同样的事情。也就是说,理解 IF 语句的工作原理有助于我们更好地实现和使用类似的指令,因为我们可以放置 #ifdef 和 #ifndef 指令来测试我们想要实现的同一代码的特定部分。
虽然我们在这里没有这样做,但你应该明白这是可能的。现在让我们回到代码本身。请注意,在第 4 行我们定义了一些内容。然而,我们可以使用 #ifdef 和 #ifndef 指令来模拟我们自己的代码。但有人可能会想,在第 4 行的定义中,我们是否在做第 6 行的事情,这是否会干扰 #ifdef 和 #ifndef 编译指令。不,不是这样的。发生这种情况是因为 #ifdef 和 #ifndef 检查是否存在定义。事实上,还有 #if 指令,但不是在 MQL5 中,而是在 C 和 C++ 中,我们可以在其中检查我们在指令中分配的值。然而,我认为出于语言简单的原因,#if 指令没有包括在内,只有 #ifdef 和 #ifndef 指令。
因此,您可以为该指令赋值,并将其转换为命名常量,如第 12 行所示,然后使用它。从现在开始,我们可以像使用常规常量一样使用该指令,因此对于那些阅读过文章并了解代码 09 工作原理的人来说,这是非常清楚的。但我们可以做一些更有趣的事情,帮助我们理解如何使用编译指令操纵数据,这对我们的日常生活很有用。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 19. } 20. //+------------------------------------------------------------------+ 21. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 22. { 23. #ifdef DEF_INTERACTIVE 24. 25. uint v, i, c; 26. 27. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 28. v += i; 29. 30. return (c == arg ? i : v); 31. 32. #else 33. 34. if (arg <= 1) 35. return arg; 36. 37. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 38. 39. #endif 40. } 41. //+------------------------------------------------------------------+
代码 10
代码10似乎非常复杂,对于那些不了解发生了什么的人来说很难理解。这似乎很难理解,但是,此代码 10 中所做的一切都不是新的,因为所做的一切都已在本文中进行了解释。但如果你不练习本文中展示的内容,可能会有点令人困惑。由于许多这些代码将在应用程序中可用,并且它们的目的只是显示应该更改的内容,因此您可以在现实生活中使用它们并研究这里显示的每个细节。
无论如何,让我们看看运行代码 10 会发生什么。首先,让我们让第 4 行指定的指令真正完成它的工作。为此,我们应该编译如上所示的代码。因此,我们得到以下结果:

图 07
现在试着理解代码更改之前发生了什么,如下面的代码片段所示。这将使未来的工作更加容易。一旦你理解了图 07 中显示的输出是如何创建的,请按照下面的代码片段修改代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. // #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ . . .
代码片段 10
请注意,下面显示的代码片段不应该像代码 10。虽然这几乎不明显,但事实上已经发生了变化。这种变化恰好发生在第 4 行,在那里我们将定义转换为注释。作为注释,编译器将忽略此行。因此,该定义似乎尚未得到实施。
虽然它看起来对代码来说毫无意义,但这个简单的事实导致编译器生成与前一个不同的代码。当执行新代码时,我们将在终端中看到下图所示:

图 08
我认为现在清楚如何使用定义和 #ifdef、#ifndef、#else 和 #endif 指令。然而,#define 指令的另一个用途仍有待讨论。记住,在文章的开头,我们提到这个指令有两个目的。第一个是我们在本文中讨论的,您可以在应用程序中练习使用代码。
这使得您可以使用 #define 指令,避免不必要地创建全局变量,同时也有利于简单、快速、有效地分析和实现同一代码的不同版本。这对于初学者来说很有价值。对于更有经验的程序员来说,这有点微不足道,因为他们几乎会自动使用这样的东西,因为它让生活变得更容易。我希望我们有来自 C 和 C++ 的 #if 指令。但没有什么可担心的,一切都是理所当然的。
使用 #define 指令的第二种方法是创建宏。然而,由于宏需要时间来分析,所以我决定暂时不包括它们,因为现在处理它们可能会使它们过于复杂而难以理解。这是因为宏并不像许多人想象的那样是简单的代码片段。如果使用得当,宏是一个非常有用的工具,但如果被误解和误用,它们会使代码变得非常混乱和复杂。
在我结束这篇文章之前,我想谈谈最后一件事。这对你来说将是一个额外的奖励,因为你有足够的耐心读到最后,并且肯定你已经想开始尝试使用 #define 指令。
这个好处是能够在 MQL5 内部创建几个简单的命令而无需进行任何更改,从而使它们更有意义。我们将在讨论完宏后对此进行更多讨论,但这已经是下一个主题的预览了。
您可能没有注意到这一点,但当我们使用 #define 指令时,我们会告诉编译器某个文本应该被另一个文本替换。有了这个概念,我们可以在不改变代码创建方式的情况下创建替代语法。
为了证明这一点,让我们考虑下面的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define BEGIN_PROC { 05. #define END_PROC } 06. #define RETURN return 07. #define ENTER_POINT void OnStart (void) 08. #define Z_SET_NUMBERS long 09. #define EQUAL == 10. #define IS = 11. #define MINOR < 12. #define OR || 13. #define MORE += 14. //+------------------------------------------------------------------+ 15. #define DEF_INTERACTIVE 16. //+----------------+ 17. #ifdef DEF_INTERACTIVE 18. #define def_OPERATION "Interactive" 19. #else 20. #define def_OPERATION "Recursive" 21. #endif 22. //+----------------+ 23. #define def_Fibonacci_Element_Default 11 24. //+----------------+ 25. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 26. //+------------------------------------------------------------------+ 27. ENTER_POINT BEGIN_PROC 28. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 29. END_PROC 30. //+------------------------------------------------------------------+ 31. Z_SET_NUMBERS Fibonacci(Z_SET_NUMBERS arg IS def_Fibonacci_Element_Default) 32. BEGIN_PROC 33. #ifdef DEF_INTERACTIVE 34. 35. Z_SET_NUMBERS v, i, c; 36. 37. for (c IS 0, i IS 0, v IS 1; c MINOR arg; i MORE v, c MORE 2) 38. v MORE i; 39. 40. RETURN (c EQUAL arg ? i : v); 41. 42. #else 43. 44. if ((arg EQUAL 1) OR (arg MINOR 1)) 45. RETURN arg; 46. 47. RETURN Fibonacci(arg - 1) + Fibonacci(arg - 2); 48. 49. #endif 50. END_PROC 51. //+------------------------------------------------------------------+
代码 11
这段代码的工作原理与代码 10 相同,但在这里我们创建了与许多人实际使用的标准截然不同的东西。但是,编译器可以很好地理解代码 11,并且能够产生与代码 10 相同的结果。
但看看它,你可能会想:“伙计,这不是 MQL5 。然而,由于我们在本文中看到的内容,我们可以仔细看看它,发现是的,虽然它不同,但代码 11 是纯粹而简单的 MQL5,只是编写方式不同,主要面向一种不那么数学化、更自然的语言。
请注意,添加定义之后(发生在第 4 行和第 13 行之间),其余代码(主要是从第 27 行开始)看起来完全不同。许多人甚至会说这无法创建可执行文件。但令所有人惊讶的是,是的,这段代码确实有效。
最后的探讨
在本文中,我们实现了一些你们可能会觉得奇怪和脱离上下文的东西,但如果应用得当,将使你的学习阶段更加有趣和令人兴奋,因为你可以构建非常有趣的东西,更好地理解 MQL5 语言本身的语法。由于这里提供的材料需要适当的研究和实践,我们将就此结束文章。在下一篇文章中,我们将讨论使用 #define 指令的第二种方法。因此,练习和学习今天的材料,以免与下一篇文章中的材料混淆。
在应用程序中,您将找到此处显示的五个代码。所以,玩得开心点。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15573
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
精通日志记录(第四部分):将日志保存到文件
黑洞算法(BHA)
从基础到中级:定义(二)
循环孤雌生殖算法(CPA)