【问题标题】:How does the Comma Operator work逗号运算符如何工作
【发布时间】:2017-11-30 09:57:45
【问题描述】:

逗号运算符在 C++ 中是如何工作的?

例如,如果我这样做:

a = b, c;  

a 最终等于 b 还是 c?

(是的,我知道这很容易测试 - 只需在此处记录以供某人快速找到答案。)

更新:这个问题暴露了使用逗号运算符时的细微差别。只是为了记录这一点:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

这个问题实际上是受到代码中的一个错字的启发。打算是什么

a = b;
c = d;

变成了

a = b,    //  <-  Note comma typo!
c = d;

【问题讨论】:

  • 在此处了解更多信息。 stackoverflow.com/questions/12824378/…
  • What does the comma operator `,` do in C? 的可能重复项。它打败了你一天。而 lillq 的回答提供了关于 a = (b, c); 的问题的答案。
  • 但在这种情况下,a = b, c = d; 实际上与预期的a = b; c = d; 执行相同?
  • @NargothBond 不一定。如果bd 是使用(和修改)公共状态的函数评估,则直到C++17 才定义执行顺序。

标签: c++ comma-operator


【解决方案1】:

请注意逗号运算符可能在 C++ 中被重载。因此,实际行为可能与预期大相径庭。

例如,Boost.Spirit 非常巧妙地使用逗号运算符来实现符号表的列表初始值设定项。因此,它使以下语法成为可能且有意义:

keywords = "and", "or", "not", "xor";

请注意,由于运算符优先级,代码(故意!)与

(((keywords = "and"), "or"), "not"), "xor";

也就是说,第一个调用的运算符是keywords.operator =("and"),它返回一个代理对象,在该对象上调用剩余的operator,s:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");

【讨论】:

  • 嗯,你不能改变优先级,这意味着你应该在你的列表周围加上括号。
  • @Jeff 相反。用括号括住列表,这将不起作用,因为那时编译器只会看到两个 char[] 之间的逗号运算符,不能重载。代码有意首先调用operator=,然后为每个剩余元素调用operator,
【解决方案2】:

逗号运算符在所有 C/C++ 运算符中具有最低优先级。因此,它始终是绑定到表达式的最后一个,这意味着:

a = b, c;

相当于:

(a = b), c;

另一个有趣的事实是逗号运算符引入了sequence point。这意味着表达式:

a+b, c(), d

保证对其三个子表达式(a+bc()d)按顺序进行评估。如果它们有副作用,这很重要。通常允许编译器以他们认为合适的任何顺序评估子表达式;例如,在函数调用中:

someFunc(arg1, arg2, arg3)

参数可以按任意顺序计算。请注意,函数调用中的逗号是 not 运算符;它们是分隔符。

【讨论】:

  • 值得指出,, 的优先级如此之低,甚至落后于 自身 ;) ...即:comma-as-operator i> 的优先级低于逗号作为分隔符。因此,如果您想在单个函数参数、变量赋值或其他逗号分隔列表中使用逗号作为运算符 - 那么您需要使用括号,例如:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
【解决方案3】:

等于b

逗号运算符的优先级低于赋值。

【讨论】:

    【解决方案4】:

    逗号运算符:

    • 优先级最低
    • 是左关联的

    为所有类型(内置和自定义)定义了一个默认版本的逗号运算符,它的工作方式如下 - 给定exprA , exprB

    • exprA 被评估
    • exprA 的结果被忽略
    • exprB 被评估
    • exprB 的结果作为整个表达式的结果返回

    对于大多数运算符,允许编译器选择执行顺序,甚至在不影响最终结果的情况下甚至需要跳过执行(例如false &amp;&amp; foo() 将跳过对foo 的调用)。但是,逗号运算符并非如此,上述步骤将始终发生*

    实际上,默认逗号运算符的工作方式与分号几乎相同。不同之处在于,用分号分隔的两个表达式形成两个单独的语句,而逗号分隔将所有表达式保留为一个表达式。这就是为什么有时会在以下场景中使用逗号运算符的原因:

    • C 语法需要单个表达式,而不是语句。例如在if( HERE )
    • C 语法需要一个语句,而不是更多,例如在for循环的初始化for ( HERE ; ; )
    • 当您想跳过花括号并保留单个语句时:if (foo) HERE ;(请不要这样做,这真的很难看!)

    当语句不是表达式时,分号不能用逗号代替。例如,这些是不允许的:

    • (foo, if (foo) bar)if 不是表达式)
    • int x, int y(变量声明不是表达式)

    在你的情况下,我们有:

    • a=b, c;,等效于a=b; c;,假设a 的类型不会重载逗号运算符。
    • a = b, c = d; 等效于 a=b; c=d;,假设 a 的类型不会重载逗号运算符。

    请注意,并非每个逗号实际上都是逗号运算符。一些具有完全不同含义的逗号:

    • int a, b; --- 变量声明列表以逗号分隔,但这些不是逗号运算符
    • int a=5, b=3; --- 这也是逗号分隔的变量声明列表
    • foo(x,y) --- 逗号分隔的参数列表。事实上,xy 可以按任何顺序计算!
    • FOO(x,y) --- 逗号分隔的宏参数列表
    • foo&lt;a,b&gt; --- 逗号分隔的模板参数列表
    • int foo(int a, int b) --- 逗号分隔的参数列表
    • Foo::Foo() : a(5), b(3) {} --- 类构造函数中以逗号分隔的初始化列表

    * 如果您应用优化,这并不完全正确。如果编译器识别出某段代码对其余部分完全没有影响,它将删除不必要的语句。

    延伸阅读:http://en.wikipedia.org/wiki/Comma_operator

    【讨论】:

    • 值得注意的是,如果operator , 被重载,您将失去任何对关联性的保证(就像您失去operator&amp;&amp;operator|| 的短路属性一样,如果它们被重载) ?
    • 逗号运算符是左关联的,无论它是否重载。表达式a, b, c 总是意味着(a, b), c 而绝不是a, (b, c)。如果元素的类型不同,后一种解释甚至可能导致编译错误。您可能追求的是参数的评估顺序?我对此不确定,但也许你是对的:c 可能会在 之前 (a, b) 被评估,即使逗号是左关联的。
    • 只是对类构造函数中逗号分隔的初始化列表的一点评论,顺序不是由列表中的位置决定的。顺序由类的声明位置决定。例如。 struct Foo { Foo() : a(5), b(3) {} int b; int a; }a(5) 之前评估 b(3)。如果您的列表是这样的,这很重要:Foo() : a(5), b(a) {}。 b 不会设置为 5,而是 a 的未初始化值,您的编译器可能会或可能不会警告。
    • 我最近遇到了一个有两个浮点数的逗号运算符,评估和丢弃一个数字有什么意义?
    • 我认为没有人能回答这个问题。您必须在上下文中显示它。可能是一个单独的问题?
    【解决方案5】:

    a 的值将是 b,但 表达式 的值将是 c。也就是说,在

    d = (a = b, c);
    

    a 将等于 bd 将等于 c

    【讨论】:

    • 几乎正确。语句没有值,表达式有。该表达式的值为 c。
    • 为什么用这个代替a = b; d = c;
    • 这让我明白了人们在谈论什么副作用。
    【解决方案6】:

    b 的值将分配给 a。 c什么都不会发生

    【讨论】:

      【解决方案7】:

      是,逗号运算符的优先级低于赋值运算符

      #include<stdio.h>
      int main()
      {
                int i;
                i = (1,2,3);
                printf("i:%d\n",i);
                return 0;
      }
      

      输出:i=3
      因为逗号运算符总是返回最右边的值。
      如果逗号运算符带有赋值运算符:

       int main()
      {
            int i;
            i = 1,2,3;
            printf("i:%d\n",i);
            return 0;
      }
      

      输出:i=1
      正如我们所知,逗号运算符的优先级低于赋值......

      【讨论】:

      • 那么第二个示例与仅在该行中添加i = 1; 有何不同?
      【解决方案8】:

      a 的值将等于 b,因为逗号运算符的优先级低于赋值运算符。

      【讨论】:

        【解决方案9】:

        第一件事:逗号实际上不是运算符,对于编译器来说,它只是一个标记,在上下文中与其他标记一起获得意义。

        这是什么意思,为什么要麻烦?

        示例 1:

        为了理解不同上下文中相同标记的含义之间的差异,我们看一下这个例子:

        class Example {
           Foo<int, char*> ContentA;
        }
        

        通常 C++ 初学者会认为这个表达式可以/可以比较事物,但这是绝对错误的,&lt;&gt;, 标记的含义取决于使用的上下文。

        上面例子的正确解释当然是模板的实例化。

        示例 2:

        当我们编写一个带有多个初始化变量和/或多个表达式的典型 for 循环时,我们也使用逗号:

        for(a=5,b=0;a<42;a++,b--)
           ...
        

        逗号的含义取决于使用的上下文,这里是for构造的上下文。

        上下文中的逗号实际上是什么意思?

        为了更加复杂(在 C++ 中总是如此),逗号运算符本身可以被重载(感谢 Konrad Rudolph 指出这一点)。

        回到问题,代码

        a = b, c;
        

        对编译器意味着类似

        (a = b), c;
        

        因为=令牌/运算符的priority高于,令牌的优先级。

        这在上下文中被解释为

        a = b;
        c;
        

        (请注意,解释取决于上下文,这里它既不是函数/方法调用也不是模板实例化。)

        【讨论】:

        • 当然是,也许我使用了错误的术语(对于词法分析器,它是一个标记,当然)
        • 作为一个与 operator, (原文如此)一起工作的人,逗号确实是一个运算符。
        • 虽然识别给定的逗号标记是否被识别为逗号运算符(与例如参数分隔符相反)本身可能是一个挑战,但这个问题专门关于 逗号运算符.
        猜你喜欢
        • 1970-01-01
        • 2013-01-12
        • 2010-10-04
        • 2017-07-11
        相关资源
        最近更新 更多