【问题标题】:What is the proper use of the comma operator?逗号运算符的正确用法是什么?
【发布时间】:2013-07-28 00:01:07
【问题描述】:

我看到了这段代码:

if (cond) {
    perror("an error occurred"), exit(1);
}

你为什么要这样做?为什么不只是:

if (cond) {
    perror("an error occurred");
    exit(1);
}

【问题讨论】:

  • 逗号运算符在表达式 SFINAE 之外无用。
  • 逗号运算符有时在您希望将多个操作组合成单个语句的宏的主体等上下文中很有用。否则,在循环中增加两个变量或在其他一些风格化的地方增加两个变量时会很有用。但是,一般来说,这是应该避免的;分号比逗号好。另请参阅Comma operator precedence while used with ?: operator 等问题,了解逗号运算符引起的混淆示例。
  • @JonathanLeffler , 我们也经常在 for 循环中使用
  • 我以为我在循环中用“在循环中增加两个变量时”覆盖了逗号;我没有特别提到在循环中初始化两个变量,但我希望这被隐含地涵盖(而且我没有在评论中留下那么多空间)。我注意到一个不起作用的用法是if (check_for_error()) print_error("bust"), return -1;——这很遗憾,但标准拒绝它是完全合乎情理的(return 不会向它写入的函数返回值,不像调用功能等)

标签: c++ c coding-style comma-operator


【解决方案1】:

逗号运算符的合法情况很少见,但确实存在。一个例子是当你想在条件评估中发生一些事情时。例如:

std::wstring example;
auto it = example.begin();
while (it = std::find(it, example.end(), L'\\'), it != example.end())
{
    // Do something to each backslash in `example`
}

它也可以用在你只能放置一个表达式但希望发生两件事的地方。例如,以下循环在 for 循环的第三个组件中递增 x 并递减 y:

int x = 0;
int y = some_number;
for(; x < y; ++x, --y)
{
    // Do something which uses a converging x and y
}

不要去寻找它的用途,但如果合适的话,不要害怕使用它,如果你看到其他人在使用它,也不要被抛出循环。如果您有两件事没有理由不单独声明,请将它们单独声明,而不是使用逗号运算符。

【讨论】:

  • 比利,赋值的结果不是它的最新值吗?由于您在分配后立即重新评估it,因此您可以在不使用逗号运算符的情况下添加测试。 (不过,这是一个有效的例子。)
  • @Jongware:是的,在这种特定情况下,您可以这样做。就个人而言,我发现逗号比在条件中赋值更易读(因为可能会混淆===)。但这是一种风格选择。
  • Ta。为了便于阅读,我通常会尽量避免 both 结构 ;-)
  • @Jongware:是的。大约只有一次我喜欢看到这是在循环中,如果它允许在循环的第一行内表达循环的整个迭代模式。 (这样您就不必搜索整个循环体并尝试遵循更复杂的迭代模式)
  • @BillyONEal 无论哪种方式,您都会在某种情况下产生副作用,这是需要避免的。这是一个很好的例子,说明逗号运算符使编写糟糕的代码变得更容易。
【解决方案2】:

在您的示例中,它根本没有任何理由。有时写成这样很有用

if(cond)
  perror("an error occured"), exit(1) ;

-- 那么你就不需要花括号了。但这是灾难的邀请。

逗号运算符是将两个或多个表达式放在引用只允许一个的位置。在您的情况下,无需使用它;在其他情况下,例如在 while 循环中,它可能很有用:

while (a = b, c < d)
  ...

while 循环的实际“评估”仅由最后一个表达式控制。

【讨论】:

  • 换句话说,逗号操作符主要用于混淆。
  • 逗号运算符组合了两个或多个表达式,而不是语句。
  • @JamesKanze: 或者宏 - #define show_error(str, code) perror(str), exit(code) 然后 show_error 表现得像函数 if (cond) show_error("an error occured", 1);。另请参阅 Grijesh Chauhan 的回答。
  • @MaciejPiechotka 您显示的宏当然不是函数。至少在 C++ 中(以及在现代 C 中),它应该被写成一个内联函数,以确保它确实表现得像一个函数。 (在旧的 C 中,它会写成do { if ( cond ) { char const* p = str; perror( p ); exit( code ); } while ( 0 ),以便表现得像一个函数。那里也没有逗号运算符。
【解决方案3】:

逗号运算符允许将表达式分组到预期的位置。

例如在某些情况下它可能很有用:

// In a loop
while ( a--, a < d ) ...

但在你的情况下,没有理由使用它。会很混乱……就是这样……

在你的情况下,这只是为了避免花括号:

if(cond)
    perror("an error occurred"), exit(1);

// =>
if (cond)
{
    perror("an error occurred");
    exit(1);
}

comma operator 文档的链接。

【讨论】:

  • 你的第二个例子(int a = 4, b = 5;)不是赋值而是初始化;该运算符不是逗号运算符(因为所有定义都用逗号分隔)。
【解决方案4】:

在您的情况下,逗号运算符是无用的,因为它可以用来避免curly braces,但事实并非如此,因为作者已经把它们放在了。因此,它无用并且可能会造成混淆

【讨论】:

    【解决方案5】:

    逗号运算符的主要用途是混淆;它允许做两个 读者只期望一个的东西。最常见的之一 用途——给一个条件添加副作用,属于这个 类别。有一些情况可能被认为是有效的, 但是:

    在 K&R 中用来表示它的那个:递增 2 for 循环中的变量。在现代代码中,这可能发生在 类似std::transformstd::copy 的函数,其中输出迭代器 与输入迭代器同时递增。 (更常见的是, 当然,这些函数将包含一个while 循环,其中 在循环结束时在单独的语句中递增。在这样的 在这种情况下,使用逗号而不是两个语句是没有意义的。)

    另一个想到的情况是输入参数的数据验证 在初始化列表中:

    MyClass::MyClass( T const& param )
        : member( (validate( param ), param) )
    {
    }
    

    (假设validate( param ) 将抛出异常,如果 出了点问题。)这种用法并不是特别吸引人,尤其是 因为它需要额外的括号,但没有很多替代方案。

    最后,我有时会看到约定:

    ScopedLock( myMutex ), protectedFunction();
    

    ,这避免了为ScopedLock 发明一个名称。告诉 说实话,我不喜欢它,但我已经看到它使用过,还有另一种选择 添加额外的大括号以确保ScopedLock 立即 destructed 也不是很漂亮。

    【讨论】:

    • "逗号运算符的主要用途是混淆"——我认为这不是真的。它当然可以以这种方式使用,但是有很多合法的非混淆用途。 (如果您将观察范围仅限于初学者编写的代码,那么您可能是对的。)
    • @KeithThompson 我看到的主要用途是混淆。但是,我确实给出了几个例子,说明它的使用可能是合理的。替代方案并不比使用逗号运算符更清楚。但是它被滥用了很多,并且在其他示例中发布的大多数示例都是滥用。 (有趣的是,它在 C++ 中比在 C 中更常被滥用。在 C++ 中,您可以重载它,而所有我见过的重载用法都是滥用。)
    【解决方案6】:

    这可以通过一些例子更好地理解:

    第一: 考虑一个表达式:

       x = ++j;
    

    但是暂时,如果我们需要分配一个临时调试值,那么我们可以写。

       x = DEBUG_VALUE, ++j; 
    

    第二:
    逗号, 运算符在for() 循环中经常使用,例如:

    for(i = 0, j = 10; i < N; j--, i++) 
     //      ^                   ^     here we can't use ;  
    

    第三:
    再举一个例子(实际上可能会觉得这样做很有趣):

    if (x = 16 / 4), if remainder is zero then print  x = x - 1;  
    if (x = 16 / 5), if remainder is zero then print  x = x + 1;
    

    也可以一步完成;

      if(x = n / d, n % d) // == x = n / d; if(n % d)
        printf("Remainder not zero, x + 1 = %d", (x + 1));
      else
        printf("Remainder is zero,  x - 1 = %d", (x - 1));
    

    PS: 有趣的是,有时使用, 运算符会带来灾难性的后果。例如在问题Strtok usage, code not working 中,错误地,OP 忘记写函数的名称,而不是写tokens = strtok(NULL, ",'");,他写了tokens = (NULL, ",'"); 并且他没有收到编译错误——但它是@987654332 的有效表达式@ 在他的程序中造成了无限循环。

    【讨论】:

    • 我认为您的第二个示例 (for) 占合法逗号运算符使用的 99%。
    • @ugoren 是的。 , 的其他用途只是一种混淆,例如 3rr 示例。
    【解决方案7】:

    运算符,() 的实际用途似乎很少。

    Bjarne Stroustrup,C++ 的设计和演变

    大部分常用的逗号可以在维基百科文章Comma_operator#Uses中找到。

    我在使用 boost::assign 时发现了一个有趣的用法,它明智地重载了运算符,使其表现为逗号分隔的值列表,可以推到向量对象的末尾

    #include <boost/assign/std/vector.hpp> // for 'operator+=()'
    using namespace std;
    using namespace boost::assign; // bring 'operator+=()' into scope
    
    {
        vector<int> values;  
        values += 1,2,3,4,5,6,7,8,9; // insert values at the end of the container
    }
    

    不幸的是,一旦编译器开始支持Uniform Initialization,上述流行于原型设计的用法现在看起来已经过时了

    这让我们回到

    运算符,() 的实际用途似乎很少。

    Bjarne Stroustrup,C++ 的设计和演变

    【讨论】:

      【解决方案8】:

      boost::assign 重载了逗号运算符以实现这种语法:

      vector<int> v; 
      v += 1,2,3,4,5,6,7,8,9;
      

      【讨论】:

        【解决方案9】:

        如果您想在条件为 truefalse 时执行两条或更多条指令,这对于 iterary operator 可能很有用。但请记住,返回值将是最右边的表达式,这是由于 逗号运算符从左到右的评估规则(我的意思是在括号)

        例如:

        a<b?(x=5,b=6,d=i):exit(1);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-08
          • 2010-09-14
          • 2010-09-08
          相关资源
          最近更新 更多