基本概念
在讨论特定运算符组之前,我们应该介绍一些基本概念,这些是所有运算符的内在概念,并将影响它们在特定上下文中的适用性和行为。
首先,根据所需操作数的数量,运算符可以分为“一元”和“二元”。顾名思义,一元运算符处理一个操作数,而二元运算符处理两个操作数。在二元的情况下,运算符总是放在操作数之间。在一元运算符中,有些运算符必须放在操作数之前,有些则必须放在操作数之后。例如,一元减号 (-) 运算符允许对值的符号取反:
int x = 10;
|
同时,还有一个使用同一个字符 - 的二元运算符,用于减法运算。
int z = x - y; // 10 - -10 -> 20 |
编译器能否在特定上下文中选择正确运算符(操作)取决于在表达式中使用该运算符的上下文。
每个运算符都被分配了优先级。优先级决定了在有多个运算符的复杂表达式中计算运算符的顺序。最先计算优先级最高的运算符,最后计算优先级最低的运算符。例如,在表达式 1 + 2 * 3 中,有两个运算(加法和乘法)和三个操作数。由于乘法的优先级高于加法,所以会先计算 2 * 3 的乘积,然后再加 1。
稍后,我们将提供一份完整的运算表(带优先级)。
此外,每个运算符都具有结合性特征。结合性可以是左或右,决定了具有相同优先级的连续运算符的执行顺序。例如,表达式 10 - 7 - 1 理论上可以用两种方法计算:
- 从 10 中减去 7,然后从得到的 3 中减去 1,最后得到 2;或者
- 7 减 1 得 6,10 减 6 得 4。
在第一种情况下,计算是从左到右执行的,这对应于左结合性;由于减法运算具有左结合性,第一个答案是正确的。
第二种计算方式对应于右结合性,不会被使用。
我们来考虑另一个同时涉及优先级和结合性的例子: 11 + 5 * 4 / 2 + 3。两种类型的运算(即加法和乘法)都是从左向右执行的。如果优先级没有区别,我们将得到 35,但正确答案是 24。改变右边的结合性将得到 14。
要显式重新定义表达式中的优先级,可以使用括号,例如: (11 + 5) * 4 / (2 + 3)。括号中的内容是之前计算的,中间结果被替换到表达式中用于其他运算。圆括号中的组可以嵌套。有关更多详细信息,请参见 用圆括号分组一节。
右结合运算符可以用逻辑否定一元运算符 ! 来举例说明。本质上,它的任务是将 false 变为 true,或者反向转换。类似其他一元运算符,结合性在此上下文中意味着操作数必须放置在运算符的哪一边。! 符号放在操作数之前,即操作数在右边。
int x = 10;
|
在这种情况下,逻辑否定被执行两次:第一次对变量 x(右侧 !),第二次针对前面的否定结果(左侧 !)。这种双重否定通过先转换为 bool 类型再反向转换的方式,可将任何非零至变换为 1。
最终的运算表也将显示结合性。
最后需要注意的是,处理表达式时,计算操作数的顺序也很重要。请勿将其与属于运算符(而非操作数)的优先级混淆。二元运算的操作数的计算顺序未明确定义,这给了编译器优化代码和提高效率的空间。编译器只保证会在执行运算之前计算操作数。
仅有有限的运算符定义了操作数求值顺序。特别地,对于逻辑与 (&&) 和或 (||),运算顺序是从左到右的,若左侧操作数的值已能确定最终结果,右侧操作数可以被省略。但对于 三元条件运算符 ?: 来说,运算顺序更加复杂:首先计算第一个条件,然后根据结果为真或假计算两个分支中的其中一个分支。更多详细信息,参见后续章节。
操作数的求值顺序取可以通过表达式中包含多个函数 调用 的情况来说明。例如,假设表达式中使用了 4 个函数:
a() + b() * c() - d() |
优先级和结合性规则仅适用于调用这些函数的中间结果,而函数调用本身的顺序可由编译器根据源代码特性和编译器设置,以其“认为有必要”的任意顺序生成。例如,乘法中涉及到的函数 b 和 c 可能是按照 [b(), c()] 的顺序调用的,也可能是按相反顺序 [c(), b()]。如果这些函数在执行过程会影响相同数据,则表达式计算后的状态无法确定。
使用数组和递增运算符时,也会出现类似的问题(参见 递增和递减)。
int i = 0;
|
根据是左侧还是右侧求差操作数最先被计算,我们可以得到 -1 (a[1] - a[2]) 或者 +1 (a[2] - a[1])。由于 MQL5 编译器一直在改进,所以不能保证当前的结果 (-1) 在未来版本中保持不变。
为了避免潜在的问题,如果某个操作数已经在同一个表达式中被修改,建议不要重复使用该操作数。
在任何表达式中,通常都会包含不同类型的操作数。这导致在对操作数执行任何操作之前,需要将其转换为某种常见类型。如果没有显式类型转换,必要时 MQL5 会执行隐式转换。此外,不同类型组合的转换规则也不相同。显式和隐式类型转换将在 相关章节中介绍。