条件三元运算符

三元条件运算符允许在一个表达式中根据特定条件描述两个计算选项。该运算符的语法如下:

condition ? expression_true : expression_false

必须在第一个操作数 'condition' 中指定逻辑条件。可以是 比较运算逻辑运算的任意组合。两个分支都必须存在。

如果条件为真,将计算 expression_true 表达式,如果条件为假,将计算 expression_false 表达式。

该运算符保证 expression_trueexpression_false 表达式中只有一个被执行。

两个表达式的类型必须相同,否则,将会尝试对其进行 隐式类型强制转换

请注意,MQL5 中表达式的处理结果始终代表一个右值(在 C++ 中,如果表达式中只有左值,那么运算符的结果也将是左值)。因此,以下代码在 C++ 中正常编译,但在 MQL5 中则会出现错误:

int x1y1; ++(x1 > y1 ? x1 : y1); // '++' - l-value required

条件运算符可以嵌套,也就是允许将另一个条件运算符用作条件或分支(expression_trueexpression_false)。同时,如果不使用圆括号来明确表示分组,条件之间的关系难免出现混乱。我们来分析 ExprConditional.mq5 中的例子。

int x = 1y = 2z = 3p = 4q = 5f = 6h = 7;
int r0 = x > y ? z : p != 0 && q != 0 ? f / (p + q) : h// 0 = f / (p + q)

在本例中,第一个逻辑条件表示 x > y 比较运算。如果为真,则执行带有 z 变量的分支。如果为假,则检查附加逻辑条件 p != 0 && q != 0(附带两个表达式选项)。

下面还有几个运算符,其中逻辑条件用大写字母表示,而计算选项用小写字母表示。为了简单起见,它们都被设为变量(来自上面的例子)。实际上,这三个组成部分中的任何一个都能成为更复杂的表达式。

对于每个字符串,你可以跟踪获得结果的方法(注释中显示了跟踪过程)。

bool A = falseB = falseC = true;
int r1 = A ? x : C ? p : q;                              // 4
int r2 = A ? B ? x : y : z;                              // 3
int r3 = A ? B ? C ? p : q : y : z;                      // 3
int r4 = A ? B ? x : y : C ? p : q;                      // 4
int r5 = A ? f : h ? B ? x : y : C ? p : q;              // 2

由于该运算符具有右结合性,所以复合表达式是从右向左分析的,即最右侧的结构体包含三个操作数并用 '?' 和 ':' 进行组合,这个结构体成为外部条件的操作数(写在左侧)。然后基于这种替换,再次从右向左解析表达式,以此类推,直到获得最后完整的上层结构 '?:'。

因此,上述表达式的分组方式如下(圆括号代表编译器的隐式解释;但是可以在表达式中添加这类圆括号来提升源代码的易读性,实际上也建议使用这种方法)。

int r0 = x > y ? z : ((p != 0 && q != 0) ? f / (p + q) : h);
int r1 = A ? x : (C ? p : q); 
int r2 = A ? (B ? x : y) : z
int r3 = A ? (B ? (C ? p : q) : y) : z
int r4 = A ? (B ? x : y) : (C ? p : q); 
int r5 = (A ? f : h) ? (B ? x : y) : (C ? p : q); 

对于 r5 变量,第一个条件 A ? f : h 计算后续表达式的逻辑条件,因此被转换为 bool。由于 A 等于 false,因此取 h 变量的值。h 不等于 0;因此,第一个条件被判定为真。这导致执行 (B ? x : y) 分支,由于 B 为 false,因此从该分支返回 y 变量的值。

运算符中必须包含所有 3 个组成部分(一个条件和 2 个备选)。否则,编译器将生成错误“意外标记”:

// ';' - unexpected token
// ';' - ':' colon sign expected
int r6 = A ? B ? x : y// lack of alternative

从编译器的角度来看,“标记”是源代码不可分割的一部分,有其独立的含义或用途,如类型、标识符、标点符号等。编译器将整个源代码分成标记序列。之前介绍的运算符符号也是标记。在上述代码中,有两个 ? 符号,那么必须有两个 : 符号与之匹配,然而实际上只有一个。因此,编译器会提示语句结束符号 ; 提前出现,并“询问”具体缺失的内容:“需要冒号”。

由于条件运算符的优先级非常低(在整张表中为 13,参见 运算优先级),因此建议用将条件运算符用圆括号括起来。这样有助于避免条件运算符的操作数被优先级更高的相邻运算“捕获”的情况。例如,如果我们通过两个三元运算符之和来计算特定变量 w 的值,可以采用下面这种简单方法:

int w = A ? f : h + B ? x : y;                           // 1

计算方式会出乎我们的意料。由于求和表达式 h + B 的优先级更高,因此被视为单个表达式。由于从右向左解析,这个求和作为一个条件出现,并被转换为 bool 类型,编译器甚至对此发出警告“表达式不是布尔型”。使用圆括号可以直观展现编译器的解释逻辑:

int w = A ? f : ((h + B) ? x : y);                       // 1

为了解决这个问题,我们应该用自己的方式添加圆括号。

int v = (A ? f : h) + (B ? x : y);                       // 9

深度嵌套条件运算符会对代码的可理解性产生负面影响。建议最多只嵌套两层或三层。