为什么要先++x || ++y && ++z
计算++x
,即使运算符的优先级&&
高于||
?
11 回答
由于&&
优先级高于||
,这意味着您的表达式等效于:
++x || (++y && ++z)
因此,在程序甚至开始评估 的结果之前&&
,它必须评估++x
以确定是否应该评估 的右侧操作数||
(逻辑二元运算符是“短路的”,这意味着它们不评估右侧如果左侧足以确定结果)。这里没有任何可疑或特定于编译器的内容。此表达式的行为完全由 C 标准指定,并且在任何编译器上都是相同的。x
如果、y
和z
都是同一个变量,它甚至会起作用,因为||
和&&
引入了序列点。
Unwind、R和其他人已经解释了真正发生的事情。所以让我补充一下:
你问题的前提是错误的。具有较高优先级的事实&&
并不意味着围绕它的操作数必须在表达式中具有较低优先级的任何操作数之前计算。即使在特殊情况下短路,||
也不&&
一定如此。
例如,考虑a=b+c+d*e
;*
具有比 更高的优先级+
,但这并不意味着d*e
必须在 之前评估b+c
。它只是意味着在我们将乘积作为一个整体添加到表达式之前必须对其进行评估。编译器可以将此表达式计算为temp1=d*e
, temp2=b+c
,a=temp1+temp2
或者它可以计算temp1=b+c
, temp2=d*e
, a=temp1+temp2
。两者都同样有效。
由于 和 的短路行为,||
对&&
评估顺序有一些额外的限制。
附带说明:通常我会避免编写这样的代码。我可以很容易地看到另一个试图阅读这段代码的程序员对增量何时会发生以及何时不会发生感到困惑。好吧,也许如果你使用真正的变量名,它看起来就不会那么奇怪了。
我偶尔会依靠短路来防止副作用。像
if (!eof() && readNextInt()>0)
如果我们已经在文件末尾,我依靠短路来防止读取,或者
if (confirmDelete==YES && deleteEntry()!=-1)
我依靠第一个测试来短路 false 所以我不应该在不应该的时候进行删除。但是这些例子对我来说似乎很简单,我希望任何有能力的程序员都会看到我在做什么。但是当这些例子变得神秘时,我认为它需要被打破。考虑
if (customerType==RETAIL || lastDepositAmount()>100.00)
如果lastDepositAmount()
有副作用,那么如果customerType
是零售,这种副作用永远不会发生。我认为这对读者来说不一定是显而易见的。(部分是因为函数名称暗示它正在检索数据而不执行任何更新,部分是因为客户类型和存款金额之间没有明显的关系——这听起来像是两个独立的东西。)不可否认,这是主观。但是,如果有疑问,请选择简单明了而不是微不足道的性能改进。始终选择简单和清晰,而不是“嘿,这是对晦涩功能的一种很酷的使用方式,任何阅读本文的人都会对我必须多么聪明才能很好地理解语言才能做到这一点印象深刻”。
这里有可怕的错误答案。
此表达式的求值顺序与实现无关。它定义明确!
由于&&
绑定高于||
,因此该表达式等价于++x || (++y && ++z)
。此外,两者&&
和||
都是短路的,因此如果表达式的左侧足以确定值,则永远不会评估右侧。
这是什么时候?好吧,我们知道无论 的值如何,它False && a
总是解析为,特别是即使 if不是单个值而是一个复杂的表达式。因此,我们根本不评估。同样,始终解析为.False
a
a
a
True || a
True
在这个例子中,这导致了一个非常明确的评估顺序:首先 ++x
,然后(如果有的话)++y
,然后(再次:如果有的话)++z
。
运算符使用波兰树数据结构进行评估,如图所示,这是一种深度优先评估算法。让我们为这些操作命名: A = ++x B = ++y C = ++z D = B && C E = C || D 评估顺序:A、B、C、D、E。当然,C 有一个优化,如果 A 为真,则 E 将被评估为真,而不评估 BC 和 D。同样,如果 B 为假,则 C 赢'不被评估以优化评估速度。
++ 具有最高优先级,作为其预增量。这意味着此操作的结果值将传递给二元运算符。然而有趣的是 ++x or'ed 与其余部分的短路。如果 ++x 为真,那么其余的可能根本不会完成。
优先级和评估顺序不是一回事,尤其是在这种情况下。所有优先级都告诉你表达式应该被解析为
++x || (++y && ++z)
IOW,这意味着被 OR'd 在一起的表达式是++x
and (++y && ++z)
。这并不意味着会(++y && ++z)
在之前进行评估++x
。
对于逻辑运算符||
and &&
,评估总是从左到右(在线 C 标准,第 6.5.13 和 6.5.14 节)。对于任何表达式
a || b
a
将被全面评估;b
除非结果为 0,否则不会被评估a
(想想a
代表++x
和b
代表++y && ++z
)。同样,对于任何表达式
a && b
a
将被全面评估;除非结果不为 0 b
,否则不会被评估。a
++x
因此,对于您的情况,首先评估表达式。如果结果为 0,则仅++y && ++z
计算结果。
||
并且&&
特别之处在于保证评估的顺序是从左到右的。对于按位或算术运算符,情况并非如此。例如,在表达式
++x + ++y * ++z
表达式++x
, ++y
, 和++z
可以按任何顺序计算;所有优先级都告诉您的是,结果(y+1) * (z+1)
将被添加到结果中x+1
。
前两个答案很好地解释了它......我要指出 || 是“否则”。
如果左侧为真,则右侧不会被触及。如果右侧为假,则 && (AndAlso) 的左侧不会被触及。
如果您希望双方都增加,请使用 | 和 &。
http://msdn.microsoft.com/en-us/library/2h9cz2eb(v=VS.80).aspx
如果编译的代码可以根据另一个表达式的结果绕过一个表达式的求值,则称逻辑操作为短路。如果第一个表达式求值的结果决定了运算的最终结果,则不需要对第二个表达式求值,因为它不能改变最终结果。如果绕过的表达式很复杂,或者涉及过程调用,短路可以提高性能。
MSDN 有显示部件如何“(未评估)”的表格。
如果第 1 部分预先确定了结果,为什么还要触及第 2 部分?
自从我使用 C 以来已经有很长时间了,但如果我没记错的话,++ 是一个一元运算符,它总是比二元运算符先例。如果您考虑一下,这是有道理的。
如果您的操作对顺序敏感,则不应依赖感知的运算符优先级,因为不同的编译器(以及同一编译器的不同版本)可能会以不同的方式实现优先级。
无论哪种方式 b 定义 ++x 意味着 x 必须在使用之前递增,因此您希望它在执行中被赋予高优先级。
不不不
if (++x || ++y && ++z) { /* ... */ }
是的
++x;
++y;
++z;
if (x || (y && z)) { /* ... */ }
同行投诉后编辑:)
是的!
if (is_data_valid(x, y, z)) process_data(&x, &y, &z);