该语言定义的a && b && c
是(a && b) && c
还是a && (b && c)
?
a && b && c
被解释为(a && b) && c
或a && (b && c)
之间是否存在可观察到的差异?
答案 0 :(得分:10)
§5.14/ 1:“&&&运算符组从左到右。[...]与&,&&&&&>从左到右 评估:如果第一个操作数为false,则不评估第二个操作数。“
关于何时或如何重要:我不确定它是否真的适用于内置类型。但是,它可能会以一种让它变得重要的方式使其超载。例如:
#include <iostream>
class A;
class M {
int x;
public:
M(int x) : x(x) {}
M &operator&&(M const &r);
M &operator&&(A const &r);
friend class A;
};
class A {
int x;
public:
A(int x) : x(x) {}
A &operator&&(M const &r);
A &operator&&(A const &r);
operator int() { return x;}
friend class M;
};
M & M::operator&&(M const &r) {
x *= r.x;
return *this;
}
M & M::operator&&(A const &r) {
x *= r.x;
return *this;
}
A &A::operator&&(M const &r) {
x += r.x;
return *this;
}
A &A::operator&&(A const &r) {
x += r.x;
return *this;
}
int main() {
A a(2), b(3);
M c(4);
std::cout << ((a && b) && c) << "\n";
std::cout << (a && (b && c)) << "\n";
}
结果:
9
16
警告:这只显示如何成为重要事项。我不特别推荐任何人这样做,只表明如果你想要的话,你可以创造一个有所作为的情况。
答案 1 :(得分:5)
实际上,从左到右计算表达式非常重要。这用于某些表达式中的短路。这是一个重要的案例:
vector<vector<int> > a;
if (!a.empty() && !a[0].empty() && a[0].back() == 3)
我打赌你每天写几次类似的陈述。如果没有定义关联性,那将会遇到很大麻烦。
答案 2 :(得分:5)
&&
和||
运算符短路:如果左侧的操作数确定整个表达式的结果,则右侧的操作数甚至不会被评估。
因此,数学家会将它们描述为左联想,例如
a && b && c
⇔(a && b) && c
,因为数学家认为a
和b
是第一位的。但是,出于教学目的,编写a && (b && c)
可能会有用,强调如果b
c
和a
都不会被评估是假的。
C中的括号仅在覆盖优先级时更改评估顺序。两个a && (b && c)
并且(a && b) && c
将首先评估为a
,然后b
,然后c
。同样,两者的评价顺序
a + (b + c)
和(a + b) + c
未指定。对比a + (b * c)
与(a + b) * c
,编译器仍然可以按任意顺序自由评估a
,b
和c
,但括号确定是否为乘法或者首先添加。还要对比FORTRAN,其中至少在某些情况下必须首先评估括号表达式。
答案 3 :(得分:2)
如果a && b
为false,则永远不会测试&& c
部分。所以是的,它确实很重要(至少在你需要从左到右排序你的操作)。 &&
本质上是一种关联操作。
关联属性
在一行中包含两次或多次出现的表达式中 相同的关联运算符,即运算的顺序 只要操作数的顺序是,执行无关紧要 没有改变。也就是说,在这样的情况下重新排列括号 表达式不会改变它的价值。
因此,如果您将其写为a && (b && c)
或(a && b) && c
,则无论如何(逻辑上)都无关紧要。两者都是等价的。但是您无法更改操作的顺序(例如a && c && b
不等同于a && b && c
)。