5

a && b && c由语言定义的意思(a && b) && c还是a && (b && c)

哇,杰瑞很快。加强这个问题:这真的重要吗?a && b && c被解释为(a && b) && c或之间会有明显的区别a && (b && c)吗?

4

4 回答 4

10

§5.14/1:“&& 运算符从左到右分组。[...] 与 & 不同,&& 保证从左到右的评估:如果第一个操作数为假,则不评估第二个操作数。”

至于何时或如何重要:我不确定它是否真的适用于内置类型。但是,有可能以一种重要的方式对其进行超载。例如:

#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

警告:这只显示了它是如何变得重要的。我并不是特别推荐任何人这样做,只是表明如果你想做得足够好,你可以创造一种情况,它会有所作为。

于 2013-11-18T16:42:44.497 回答
5

事实上,从左到右计算表达式非常重要。这在某些表达式中用于短路。这是一个重要的案例:

vector<vector<int> > a;
if (!a.empty()  && !a[0].empty() && a[0].back() == 3) 

我敢打赌,你每天写几次类似的陈述。如果没有定义关联性,您将遇到巨大的麻烦。

于 2013-11-18T16:43:58.090 回答
5

和运算&&||短路:如果左边的操作数决定了整个表达式的结果,右边的操作数甚至不会被计算。

因此,数学家会将它们描述为左结合,例如
a && b && c(a && b) && c,因为对于数学家来说,这意味着a并被b首先考虑。但是,出于教学目的,改为编写可能有用a && (b && c),以强调如果为假,则既不 bc不会被评估。a

C 中的括号仅在覆盖优先级时才会更改评估顺序。两者a && (b && c)
(a && b) && c都将被评估为a首先,然后b,然后c。同样,两者的评估顺序
a + (b + c)(a + b) + c未指定。对比a + (b * c)(a + b) * c,编译器仍然可以自由地以任何顺序计算abc,但括号确定乘法或加法是否首先发生。还要对比 FORTRAN,至少在某些情况下,必须首先计算带括号的表达式。

于 2013-11-18T16:44:55.790 回答
2

如果a && b为假,则&& c永远不会测试该部分。所以是的,它确实很重要(至少你需要从左到右排序你的操作)。 &&本质上是一种关联操作。

关联属性

在同一关联运算符的一行中包含两次或多次出现的表达式中,只要操作数的顺序不变,执行操作的顺序无关紧要。也就是说,在这样的表达式中重新排列括号不会改变它的值。

a && (b && c)因此,如果您将其写为or并不重要(逻辑上)(a && b) && c。两者是等价的。但是您不能更改操作的顺序(例如a && c && b不等同于a && b && c)。

于 2013-11-18T16:45:29.023 回答