关于C#:逗号运算符是什么?

What does the comma operator , do?

,操作符在c中做什么?


表达式:

1
(expression1,  expression2)

首先计算表达式1,然后计算表达式2,并为整个表达式返回表达式2的值。


我见过在while循环中使用最多:

1
2
3
4
5
string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

它将进行操作,然后根据副作用进行测试。另一种方法是这样做:

1
2
3
4
5
6
7
string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}


逗号运算符将计算左操作数,放弃结果,然后计算右操作数,这就是结果。链接中提到的惯用用法是在初始化for循环中使用的变量时,它给出了以下示例:

1
2
3
4
5
6
7
void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/
      putchar(*s);
}

否则逗号运算符的用处就不多了,尽管生成难以读取和维护的代码很容易被滥用。

根据C99标准草案,语法如下:

1
2
3
expression:
  assignment-expression
  expression , assignment-expression

第2段说:

The left operand of a comma operator is evaluated as a void expression; there is a sequence point after its evaluation. Then the right operand is evaluated; the result has its type and value. 97) If an attempt is made to modify the result of a comma operator or to access it after the next sequence point, the behavior is undefined.

脚注97说:

A comma operator does not yield an lvalue.

这意味着您不能为逗号运算符的结果赋值。

需要注意的是,逗号运算符的优先级最低,因此在某些情况下,使用()可能会产生很大的差异,例如:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf("%d %d
"
, x, y ) ;
}

将具有以下输出:

1
1 4


逗号运算符将它两边的两个表达式组合为一个表达式,按从左到右的顺序对它们进行计算。右侧的值作为整个表达式的值返回。(expr1, expr2){ expr1; expr2; }相似,但可以在函数调用或赋值中使用expr2的结果。

它经常出现在for循环中,用于初始化或维护多个变量,如下所示:

1
2
3
4
5
for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */

}

除此之外,我只在另一种情况下"愤怒地"使用它,在总结两个应该始终在宏中一起执行的操作时。我们的代码将各种二进制值复制到一个字节缓冲区中,以便在网络上发送,并且在我们到达的地方维护了一个指针:

1
2
3
4
5
6
7
unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

如果值是shorts或ints,我们这样做:

1
2
*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

后来我们读到这不是真正有效的c,因为(short *)ptr不再是l值,不能递增,尽管当时我们的编译器并不介意。为了解决这个问题,我们将表达式分为两部分:

1
2
*(short *)ptr = short_value;
ptr += sizeof(short);

然而,这种方法依赖于所有开发人员记住始终将这两个语句放在一起。我们需要一个函数,您可以在其中传递输出指针、值和值的类型。这是C,不是C++的模板,我们不能有一个任意类型的函数,所以我们在一个宏上进行了讨论:

1
#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

通过使用逗号运算符,我们可以在表达式中或作为我们希望的语句使用它:

1
2
3
4
5
6
if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

我不是说这些例子中的任何一个都是好的风格!事实上,我似乎还记得史蒂夫·麦康奈尔(Steve McConnell)的代码完整建议,即使在for循环中使用逗号运算符,也不要这样做:为了可读性和可维护性,循环应该只由一个变量控制,并且for行本身的表达式应该只包含循环控制代码,而不是其他额外的初始化位。R回路维护。


它导致对多个语句进行评估,但只使用最后一个语句作为结果值(我认为是右值)。

所以…

1
2
3
4
int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

应导致x设置为8。


逗号运算符没有任何意义,它是一个100%多余的特性。它的主要用途是"人们试图变得聪明",因此使用它(无意中)混淆可读代码。主要的用途是混淆循环,例如:

1
for(int i=0, count=0; i<x; i++, count++)

其中int i=0, count=0实际上不是逗号运算符,而是声明列表(这里我们已经混淆了)。i++, count++是逗号运算符,它首先计算左操作数,然后计算右操作数。逗号运算符的结果是右操作数的结果。左操作数的结果将被丢弃。

但是,如果不使用逗号运算符,上述代码可以以更可读的方式编写:

1
2
3
4
5
6
int count = 0;
for(int i=0; i<x; i++) // readable for loop, no nonsense anywhere
{
  ...
  count++;
}

我看到的逗号运算符的唯一真正用途是关于序列点的人工讨论,因为逗号运算符在左操作数和右操作数的计算之间带有序列点。

因此,如果您有一些未定义的行为代码,比如:

1
printf("%d %d", i++, i++);

实际上,您可以通过编写

1
printf("%d %d", (0,i++), (0,i++));

现在,在对i++的每个评估之间都有一个序列点,因此至少程序不会再冒崩溃和烧坏的风险,即使函数参数的评估顺序仍未明确。

当然,没有人会在实际的应用程序中编写这样的代码,它只对语言律师讨论C语言中的序列点有用。

逗号运算符被misra-c:2004和misra-c:2012禁止,理由是它创建的代码可读性较低。


正如前面的答案所述,它计算所有语句,但使用最后一个语句作为表达式的值。我个人认为它只在循环表达式中有用:

1
for (tmp=0, i = MAX; i > 0; i--)

我唯一看到它有用的地方是编写一个时髦的循环,在这个循环中,您希望在一个表达式(可能是in it表达式或循环表达式)中执行多个操作。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

如果有语法错误,或者我混合了不严格的C语言,请原谅。我不认为,运算符的格式很好,但这正是您可以使用它的原因。在上面的例子中,我可能会使用一个while循环,因此init和loop上的多个表达式会更明显。(我将以内联方式初始化i1和i2,而不是声明然后初始化….等等。)


我只是想回答@rajesh和@jeffmercado的问题,我认为这是非常重要的,因为这是搜索引擎的热门话题之一。

以下面的代码片段为例

1
2
3
4
5
int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d
"
, i , j);

它将打印

1
1 5

i案件的处理与大多数答案一样。所有表达式都按从左到右的顺序计算,但只有最后一个表达式被分配给i(表达的结果)is1`。

由于,具有最低的运算符优先级,因此j大小写遵循不同的优先级规则。由于这些规则,编译器可以看到赋值表达式、常量、常量……表达式按从左到右的顺序重新计算,其副作用保持可见,因此,由于j = 5j5

有趣的是,语言规范不允许使用int j = 5,4,3,2,1;。初始值设定项需要赋值表达式,因此不允许使用直接,运算符。

希望这有帮助。