And*_*rew 16 c++ boolean-logic boolean-expression short-circuiting
我想知道是否有人知道编译器解释以下代码的方式:
#include <iostream>
using namespace std;
int main() {
cout << (true && true || false && false) << endl; // true
}
Run Code Online (Sandbox Code Playgroud)
这是真的,因为&&的优先级高于|| 或因为|| 是一个短路运算符(换句话说,短路运算符是否忽略所有后续表达式,或只是下一个表达式)?
小智 34
&&
优先级高于||
.
Ant*_*sky 29
Caladain有正确的答案,但我想回答你对他的回答的一点评论:
如果短路|| 运算符发生并使第二个&&表达式的执行短路,这意味着|| 运算符在第二个&&运算符之前执行.这意味着&&和||从左到右执行 (不是&&优先权).
我认为你遇到的部分问题是,优先权并不意味着你认为它意味着什么.确实,&&
优先级高于||
,这恰恰说明了您所看到的行为.考虑普通算术运算符的情况:假设我们有a * b + c * (d + e)
.优先级告诉我们的是如何插入括号:首先是围绕*
,然后围绕+
.这给了我们(a * b) + (c * (d + e))
; 在你的情况下,我们有(1 && 1) || (infiniteLoop() && infiniteLoop())
.然后,想象成为树的表达.为此,将每个运算符转换为具有两个参数作为子节点的节点:
评估这个树是短路的地方.在算术树中,你可以想象一个广度优先的自下而上的执行风格:首先评估DE = d + e
,然后AB = a * b
和CDE = c * DE
,最后的结果是AB + CDE
.但请注意,你可能同样也评估了AB
第一,然后DE
,CDE
和最终结果; 你无法分辨出来.然而,由于||
并且&&
正在短路,他们必须使用这最左边的评估.因此,为了评估||
,我们首先评估1 && 1
.由于这是真的,||
短路并忽略了它的右手分支 - 尽管如果它已经评估过,它将不得不评估infiniteLoop() && infiniteLoop()
第一.
如果它有帮助,您可以将树中的每个节点视为函数调用,plus(times(a,b), times(c,plus(d,e)))
在第一种情况下产生以下表示,or(and(1,1), and(infiniteLoop(),infiniteLoop())
在第二种情况下.短路意味着你必须完全评估每个左手函数参数or
或and
; 如果是true
(for or
)或false
(for and
),则忽略右手参数.
您的评论预先假定我们首先评估具有最高优先级的所有内容,然后评估具有次高优先级的所有内容,依此类推,依此类推,这对应于树的广度优先自下而上执行.相反,发生的事情是优先级告诉我们如何构建树.在简单的算术情况下,树的执行规则是无关紧要的; 然而,短路恰好是如何评估树的精确规范.
编辑1:在你的其他一条评论中,你说
您的算术示例需要在最终添加之前评估两个乘法,是不是定义优先级的是什么?
是的,这是定义优先级的原因 - 除非它不完全正确.这当然是在完全正确Ç,但是考虑你将如何评价(非C!)表达0 * 5 ^ 7
在你的头上,在那里和优先级高于.根据您的广度优先自下而上的原则,我们需要评估和之前我们能找到的结果.但你不会费心去评价; 你只是说"好吧,因为对所有人来说,这必须是",并跳过整个右手分支.换句话说,在评估最终乘法之前,我没有完全评估双方; 我已经短路了.同样,因为和任何5 ^ 7 = 57
^
*
0
5 ^ 7
5 ^ 7
0 * x = 0
x
0
false && _ == false
true || _ == true
_
,我们可能不需要触摸右侧; 这就是操作员短路的意义.C不会短路乘法(虽然一种语言可以做到这一点),但它会短路&&
和||
.
正如短路0 * 5 ^ 7
不会改变通常的PEMDAS优先规则一样,短路逻辑运算符并不会改变&&
优先级高的事实||
.这只是一个捷径.由于我们必须首先选择运算符的某一侧进行求值,因此C承诺首先评估逻辑运算符的左侧; 一旦完成了这个,就会有一种明显的(有用的)方法来避免评估某些值的右侧,C承诺这样做.
你的规则 - 评估表达式广度 - 自下而上 - 也是明确定义的,语言可以选择这样做.然而,它具有不允许短路的缺点,这是一种有用的行为.但是如果树中的每个表达式都是明确定义的(没有循环)和纯粹的(没有修改变量,打印等),那么你就无法区分它们.只有在这些奇怪的情况下,"和"和"或"的数学定义没有涵盖,短路甚至是可见的.
另外,请注意,通过优先考虑最左边的表达式,短路的工作原理并不重要.人们可以定义一种语言Ɔ,其中??
代表and
和\\
代表||
,但在哪里0 ?? infiniteLoop()
和1 \\ infiniteLoop()
将会循环,infiniteLoop() ?? 0
并且infiniteLoop() \\ 1
将分别为假和真.这恰好对应于选择首先评估右侧而不是左侧,然后以相同的方式进行简化.
简而言之:优先级告诉我们的是如何构建解析树.评估解析树的唯一明智的命令就是那些表现得好像我们在明确定义的纯值上自上而下地评估它(如你所愿)的那样.对于未定义或不纯的值,必须选择一些线性顺序.1 一旦被选择的线性顺序,用于操作员的一侧上的某些值可以唯一地确定整个表达式的结果(例如,0 * _ == _ * 0 == 0
,false && _ == _ && false == false
,或true || _ == _ || true == true
).因此,您可以在不完成对线性顺序之后发生的任何事情的评估的情况下离开; C承诺为逻辑运算符&&
和||
通过从左到右的方式评估它们,而不是为了其他任何事情.然而,由于优先级,我们都知道,true || true && false
是true
不false
:因为
true || true && false
? true || (true && false)
? true || false
? true
Run Code Online (Sandbox Code Playgroud)
代替
true || true && false
? (true || true) && false
? true && false
? false
Run Code Online (Sandbox Code Playgroud)
1:实际上,我们理论上也可以并行地评估运算符的两边,但是现在这并不重要,对C来说当然不会有意义.这会产生更灵活的语义,但是会产生一些问题 -效果(什么时候发生?).
Cal*_*ain 22
(&& true || false && false)使用&&具有更高优先级进行评估.
TRUE && TRUE = True
FALSE && FALSE = False
True || False = True
Run Code Online (Sandbox Code Playgroud)
更新:
1&&1||infiniteLoop()&&infiniteLoop()
Run Code Online (Sandbox Code Playgroud)
为什么这在C++中生成?
和以前一样,让它分开.&&的优先级高于|| 和C++中的布尔语句短路.
1 && 1 = True.
Run Code Online (Sandbox Code Playgroud)
当bool值转换为整数值时,则
false -> 0
true -> 1
Run Code Online (Sandbox Code Playgroud)
该表达式计算此(true)&&(true)语句,该语句使||短路,从而阻止无限循环运行.还有更多的编译器Juju正在进行中,所以这是对这个例子足够的情况的简单看法.
在非短路环境中,该表达将永远挂起,因为OR的两侧将被"评估"而右侧将悬挂.
如果您对优先级感到困惑,那么如果||,这就是原始帖子中的评估方式 优先级高于&&:
1st.) True || False = True
2nd.) True && 1st = True
3rd.) 2nd && false = false
Expression = False;
Run Code Online (Sandbox Code Playgroud)
我不记得它是从右到左,还是从左到右,但无论哪种方式,结果都是一样的.在你的第二篇文章中,如果|| 有更高的优势:
1st.) 1||InfLoop(); Hang forever, but assuming it didn't
2nd.) 1 && 1st;
3rd.) 2nd && InfLoop(); Hang Forever
Run Code Online (Sandbox Code Playgroud)
tl; dr:仍然优先考虑首先评估&&,但编译器也会短路OR.从本质上讲,编译组这样的操作顺序(简单地认为,把倒在叉子:-P)
1st.) Is 1&&1 True?
2nd.) Evaluate if the Left side of the operation is true,
if so, skip the second test and return True,
Otherwise return the value of the second test(this is the OR)
3rd.) Is Inf() && Inf() True? (this would hang forever since
you have an infinite loop)
Run Code Online (Sandbox Code Playgroud)
更新#2: "但是,这个例子证明&&没有优先权,因为||在第二个&&之前被评估.这表明&&和||具有相同的优先级,并按从左到右的顺序进行评估."
"如果&&具有优先权,它将评估第一个&&(1),然后是第二个&&(无限循环)并挂起程序.由于这不会发生,因此&|在||之前不会被评估."
我们将详细介绍这些内容.
我们在这里谈论两个截然不同的事情.优先级,它决定了操作顺序,以及短路,这是一种节省处理器周期的编译器/语言技巧.
让我们首先介绍优先顺序.优先顺序是"操作顺序"的简称本质上,给出这样的陈述:1 + 2*3,操作是否应按顺序进行评估?
数学明确地将操作的顺序定义为赋予乘法优先于加法的优先级.
1 + (2 * 3) = 1 + 2 * 3
2 * 3 is evaluated first, and then 1 is added to the result.
* has higher precedence than +, thus that operation is evaluated first.
Run Code Online (Sandbox Code Playgroud)
现在,让我们转换为布尔表达式:(&& = AND,|| = OR)
true AND false OR true
Run Code Online (Sandbox Code Playgroud)
因此,C++赋予AND优先于OR的优先级
(true AND false) OR true
true AND false is evaluated first, and then
used as the left hand for the OR statement
Run Code Online (Sandbox Code Playgroud)
因此,只有优先级,(true && true || false && false)将按此顺序操作:
((true && true) || (false && false)) = (true && true || false && false)
1st Comparison.) true && true
2nd Comparison.) false && false
3rd Comparison.) Result of 1st comparison || Result of Second
Run Code Online (Sandbox Code Playgroud)
和我一起到目前为止?现在让我们进入Short Circuiting:在C++中,布尔语句被称为"短路".这意味着编译器将查看给定语句a选择"最佳路径"进行评估.举个例子:
(true && true) || (false && false)
There is no need to evaluate the (false && false) if (true && true)
equals true, since only one side of the OR statement needs to be true.
Thus, the compiler will Short Circuit the expression. Here's the compiler's
Simplified logic:
1st.) Is (true && true) True?
2nd.) Evaluate if the Left side of the operation is true,
if so, skip the second test and return True,
Otherwise return the value of the second test(this is the OR)
3rd.) Is (false && false) True? Return this value
Run Code Online (Sandbox Code Playgroud)
如您所见,如果(true && true)被评估为TRUE,那么就不需要花费时钟周期来评估if(false && false)是否为真.
C++总是简短的Circuts,但其他语言为所谓的"Eager"运算符提供机制.
以编程语言Ada为例.在Ada中,"AND"和"OR"是"渴望"操作员......他们强制要评估所有内容.
在Ada中(true和true)OR(false和false)将在评估OR之前评估(true和true)和(false和false).Ada还为您提供了使用AND THEN和OR ELSE进行短路的能力,这将为您提供与C++相同的行为.
我希望完全回答你的问题.如果没有,请告诉我:-)
更新3: 上次更新,如果您仍然遇到问题,我会继续发送电子邮件.
"如果发生||运算符的短路并使第二个&&表达式的执行短路,则意味着在第二个&&运算符之前执行了||运算符.这意味着对&&和||的从左到右执行(不是&&优先权.)"
我们来看看这个例子:
(false && infLoop()) || (true && true) = true (Put a breakpoint in InfLoop and it won't get hit)
false && infLoop() || true && true = true (Put a breakpoint in InfLoop and it won't get hit)
false || (false && true && infLoop()) || true = false (infLoop doesn't get hit)
Run Code Online (Sandbox Code Playgroud)
如果你说的是真的,InfLoop会在前两个中被击中.您还会注意到第三个示例中也没有调用InfLoop().
现在,让我们来看看:
(false || true && infLoop() || true);
Run Code Online (Sandbox Code Playgroud)
Infloop被召唤!如果OR具有比&&更高的优先级,那么编译器将评估:
(false || true) && (infLoop() || true) = true;
(false || true) =true
(infLoop() || true = true (infLoop isn't called)
Run Code Online (Sandbox Code Playgroud)
但是InfLoop被召唤了!这就是为什么:
(false || true && infLoop() || true);
1st Comparison.) true && InfLoop() (InfLoop gets called)
2nd Comparison.) False || 1st Comp (will never get here)
3rd Comparison.) 2nd Comp || true; (will never get here)
Run Code Online (Sandbox Code Playgroud)
Precendece ONLY仅设置操作分组.在此,&&大于||.
true && false || true && true gets grouped as
(true && false) || (true && true);
Run Code Online (Sandbox Code Playgroud)
编译器然后走来,并确定什么样的顺序应该在执行评估给它保存周期的最佳机会.
Consider: false && infLoop() || true && true
Precedence Grouping goes like this:
(false && infLoop()) || (true && true)
The compiler then looks at it, and decides it will order the execution in this order:
(true && true) THEN || THEN (false && InfLoop())
Run Code Online (Sandbox Code Playgroud)
这是一个事实..我不知道如何证明这一点.优先级由语言语法规则决定.编译器的优化由每个编译器确定.有些比其他编译器更好,但是所有人都可以自由地重新排序它认为合适的分组比较,以便为最快的执行提供"最佳"机会,并进行最少的比较.
两个事实解释了两个例子的行为.首先,优先级 &&
高于||
.其次,两个逻辑运算符都使用短路评估.
优先权常常与评估顺序混淆,但它是独立的.只要最终结果正确,表达式可以按任何顺序评估其各个元素.通常对于某些运算符,这意味着可以在右边的值(RHS)之前或之后评估左边的值(LHS),只要在应用运算符本身之前评估两者.
逻辑运算符具有特殊属性:在某些情况下,如果一方评估特定值,则无论另一方的值如何,都知道运算符的值.为了使这个属性有用,C语言(以及扩展每个类C语言)已经指定了逻辑运算符来在RHS之前评估LHS,并且进一步仅评估RHS是否需要它的值来知道结果.运营商.
因此,假设通常的定义TRUE
和FALSE
,TRUE && TRUE || FALSE && FALSE
是在左求开始.第一个TRUE
不强制第一个的结果&&
,因此TRUE
评估第二个,然后计算表达式TRUE && TRUE
(为TRUE).现在,它||
知道它的LHS.更好的是,它的LHS已经迫使其结果||
被人们所知,因此它忽略了对其整个RHS的评估.
完全相同的评估顺序适用于第二种情况.由于RHS与RHS ||
无关,因此不进行评估,也不进行任何调用infiniteLoop()
.
此行为是设计使然,并且很有用.例如,您可以写p && p->next
知道表达式永远不会尝试取消引用NULL指针.