简单形式的 #define
简单形式的 #define 指令将注册一个标识符和一个字符序列 - 在源代码中该指令之后,直到程序的结尾,或者在具有相同标识符的 #undef 指令之前,都会用字符序列替换标识符。
语法如下:
#define macro_identifier [text] |
文本从标识符之后开始,一直到当前行的结尾。标识符和文本必须由任意数量的空格或制表符来分隔。如果所需的字符序列太长,出于可读性考虑,可以在行结尾添加反斜杠字符 \ 将它分成几行。
#define macro_identifier text_beginning \
|
文本可以由任何语言结构组成:常量、运算符、标识符和标点符号。如果用 macro_identifier 代替源代码中找到的结构,所有这些结构都将包含在编译中。
简单形式通常用于几个目的:
- 标志声明,这随后又用于 条件编译 检查;
- 命名常量声明;
- 常用语句的简化表示。
第一个的特点是,在标识符之后不需要指定任何内容 - 只要存在带名称的指令就能注册的相应标识符,并且可以用于条件指令 #ifdef/ifndef。对这些条件指令来说,最重要的是该标识符是否存在,也就是以标志模式运行:声明/未声明。例如,以下指令定义了 DEMO 标志:
#define DEMO |
比如说,它可以用来构建一个排除了某些函数的程序的演示版本(参见条件编译一节中的例子)。
使用简单指令的第二种方法允许您用友好的名称替换源代码中的“幻数”。“幻数”是插入到源文本中的常量,其含义往往不明确(因为数字只是数字:最好至少在注释中解释)。此外,相同的值可能分散在代码的不同部分,如果程序员决定将它改为另一个值,就必须在所有地方都进行更改(希望他没有漏掉任何一处)。
有了命名宏,这两个问题就很容易解决了。例如,一个脚本可以生成一个包含斐波那契数的数组,直到达到某个最大深度。然后用预定义的数组大小定义一个宏并在数组本身的说明中使用这个宏 (Preprocessor.mq5)。
#define MAX_FIBO 10
|
如果程序员随后决定需要将数组增大多少,只需在一个位置执行即可,也就是在 #define 指令中。因此,该指令实际上定义了算法的某个参数,该参数被“硬连线”到源代码中,并且不可用于用户配置。这种需求经常出现。
人们可能会疑惑,通过 #define 进行定义与全局上下文中的常量变量到底有何不同。事实上,我们可以声明一个具有相同名称和用途的变量,甚至保留大写字母:
const int MAX_FIBO = 10; |
然而,在这种情况下,MQL5 不允许定义具有指定大小的数组,因为方括号中只允许放入常量,即字面量(尽管“常量变量”这个名字看起来像,但并不是常量)。为解决这个问题,我们可以将数组定义为动态的(不需要先指定大小),然后使用 ArrayResize 函数 - 将变量作为大小传递在这里并不困难。
定义命名常量的另一种方法是使用枚举,但仅限于整数值。例如:
enum
|
但是宏可以包含任何类型的值。
#define TIME_LIMIT D'2023.01.01'
|
在源文本中搜索宏名称进行替换时,会考虑语言的语法,也就是说,不可分元素(如变量标识符或字符串字面量)将保持不变,即使它们包含与某个宏匹配的子字符串。例如,给定以下宏 XYZ,XYZAXES 变量将保持不变,XYZ 名称(因为它与宏完全相同)将更改为 ABC。
#define XYZ ABC
|
宏替换可用于将代码嵌入到其他程序的源代码中。以 .mqh 头文件形式分发的库会采用这种技巧,这些库通过 #include 指令连接到程序。
特别是对于脚本,我们可以定义自己的 OnStart 函数库实现,它必须在不影响程序原有功能的情况下执行一些额外的操作。
void OnStart()
|
假设这部分在包含的头文件中 (Preprocessor.mqh)。
随后,预处理器会在源代码中将原始函数 OnStart(在 Preprocessor.mq5 中)重命名为 _OnStart(前提是该标识符不会在其他任何地方用于其他目的)。而头文件中新版本的 OnStart 调用 _OnStart,将其“包装”到附加语句中。
使用简单 #define 指令的第三种常见方法是缩短语言结构的表示法。例如,无限循环的标题可以用一个单词 LOOP 来表示:
#define LOOP for( ; !IsStopped() ; ) |
然后应用到代码中:
LOOP
|
这个方法也是使用 #define 指令(带参数)的主要技巧(参见下文)。