#define 作为伪函数的形式

参数形式 #define 的语法类似于函数语法。

#define macro_identifier(parameter,...) text_with_parameters

这样的宏具有一个或多个带圆括号的参数。各参数用逗号分隔。每个参数都是一个简单的标识符(通常是一个字母)。此外,一个宏的所有参数必须有不同的标识符。

标识符和左圆括号之间不能有空格,这一点很重要,否则宏将被视为一个简单的形式,并且替换文本以左圆括号开始。

该指令注册后,预处理器将在源代码中搜索以下格式的行:

macro_identifier(expression,...)

可以指定任意表达式来代替参数。自变量的数量必须与宏参数的数量相匹配。所有找到的匹配项将被替换为 text_with_parameters,其中参数将被替换为传递的表达式。每个参数可以以任何顺序出现多次。

例如,以下宏查找两个值中的较大值:

#define MAX(A,B) ((A) > (B) ? (A) : (B))

如果代码包含语句:

int z = MAX(xy);

它将被预处理器“扩展”成:

int z = ((x) > (y) ? (x) : (y));

宏替换适用于任何数据类型(在宏内部应用的操作对这些数据类型有效)。

不过这种替换也会有副作用。例如,如果实际参数是修改变量(比如++x)的函数调用或语句,那么相应的操作可以执行多次(而不是预期的一次)。如果为 MAX,这会发生两次,一次是在比较期间,另一次是在 ?: 运算符的一个分支内获取值时。这样看来,尽可能将这样的宏转换成函数是合理的(特别是考虑到 MQL5 中的函数会自动内联)。

各参数和整个宏定义都带有圆括号。使用圆括号可以确保,将表达式替换为参数或其他表达式内部的宏本身这一操作不会因不同优先级而改变计算顺序。假设宏定义了两个参数(未带圆括号)的乘积:

#define MUL(A,BA * B

那么使用带有以下表达式的宏将会产生意外的结果:

int x = MUL(1 + 23 + 4); // 1 + 2 * 3 + 4

不是进行乘法 (1 + 2) * (3 + 4) 得到 21,而是计算 1 + 2 * 3 + 4,得到 11。正确的宏定义应该是这样的:

#define MUL(A,B) ((A) * (B))

可以指定另一个宏作为宏参数。此外,还可以在宏定义中插入其他宏。所有这样的宏都将被顺序替换。例如:

#define SQ3(X) (X * X * X)
#define ABS(XMathAbs(SQ3(X))
#define INC(Y) (++(Y))

下面的代码将打印 504(MathAbs 是一个内置函数,返回一个数字的模数,即无符号数):

int x = -10;
Print(ABS(INC(x)));
// -> ABS(++(Y))
// -> MathAbs(SQ3(++(Y)))
// -> MathAbs((++(Y))*(++(Y))*(++(Y)))
// -> MathAbs(-9*-8*-7)
// -> 504

x 变量中,值 -7 将保持不变(由于三次递增)。

宏定义可以包含不配对的圆括号。这种技正确巧通常用在一对宏中,其中一个宏打开某段代码,另一个宏关闭该段代码。在这种情况下,每个宏中的不配对圆括号将会变得匹配。特别是,在MetaTrader 5 分发包的标准库文件中,Controls/Defines.mqh 中定义了 EVENT_MAP_BEGIN 和 EVENT_MAP_END。它们用于在图形对象中构建事件处理函数。

预处理器逐行读取程序的整个源文本,从 mq5 主文件开始,并在遇到头文件的位置插入文本。任何一行代码被读取时,一组已经定义好的宏就形成了。宏的定义顺序并不重要:很可能一个宏在其定义中引用了另一个宏,而被引用的宏可能在该宏的上方或下方。最重要的是,在使用宏名称的源代码行中,所有引用宏的定义都是已知的。

我们来分析一个例子。

#define NEG(x) (-SQN(x))*TEN
#define SQN(x) ((x)*(x))
#define TEN 10
...
Print(NEG(2)); // -40

这里,NEG 宏使用了在其下方描述的 SQN 和 TEN 宏。这并不妨碍我们在所有三个 #define 之后成功地在代码中使用 NEG 宏。

但是,如果我们将所有列的相对位置更改为以下:

#define NEG(x) (-SQN(x))*TEN
#define SQN(x) ((x)*(x))
...
Print(NEG(2)); // error: 'TEN' - undeclared identifier
...
#define TEN 10

我们会遇到一个“未声明的标识符”编译错误。