逗号运算符 , 是用来做什么的?

211

, 运算符在 C 语言中的作用是什么?


可能是逗号运算符的正确使用方法是什么?的重复问题。 - Sergey K.
1
正如我在答案中所指出的,左操作数的评估后有一个序列点。这与函数调用中的逗号不同,后者只是语法上的。 - Shafik Yaghmour
3
考虑到此问题的提问和回答早在其他问题之前,因此更可能是其他问题的重复。然而,另一个问题也被同时标记了[tag:c]和[tag:c++],这有些麻烦。本问题仅涉及C语言,并且有一些不错的答案。 - Jonathan Leffler
这个回答解决了你的问题吗?逗号运算符是如何工作的,它的优先级是多少? - undefined
8个回答

179
表达式:

The expression:

(expression1,  expression2)

先计算 expression1,然后计算 expression2expression2 的值将作为整个表达式的返回值。


5
如果我写 i = (5,4,3,2,1,0),那么理想情况下它应该返回0,对吗?但是i被赋值为了5?你能帮我理解错在哪里吗? - Jayesh
25
@James:逗号运算符的值将始终是最后一个表达式的值。在任何时候,i 都不会具有值 5、4、3、2 或 1,它仅仅是 0。除非这些表达式具有副作用,否则该运算符几乎没有什么用处。 - Jeff Mercado
7
请注意,在逗号表达式的左右两侧评估之间存在完整的序列点(参见Shafik Yaghmour的[答案](https://dev59.com/OnVD5IYBdhLWcg3wNIzc#18444099)引用C99标准)。这是逗号运算符的一个重要属性。 - Jonathan Leffler
看 https://learn.microsoft.com/en-us/cpp/cpp/comma-operator,如果不使用括号似乎会有所不同。在 i = b, c 的情况下,b 被分配给了 i。这是如何发生的?在函数参数的情况下,必须使用括号,Microsoft 明确解释了这一点。 - Rajesh
9
i = b, c; 相当于 (i = b), c,因为赋值运算符 = 的优先级高于逗号运算符 ,。逗号运算符的优先级最低。 - cyclaminist
1
我担心括号会产生两个误导:(1) 它们不是必要的——逗号运算符不必被括在括号里;(2)它们可能会与函数调用的参数列表周围的括号混淆——但参数列表中的逗号不是逗号运算符。然而,修复它并不完全简单。也许这样说比较好:“在语句expression1, expression2;中,首先计算 expression1,可能是为了其副作用(如调用函数),然后有一个序列点,然后计算 expression2 并返回值…” - Jonathan Leffler

151

我经常在while循环中看到它的使用:

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

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

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}

28
嘿,那真是个不错的想法!我经常需要在循环中做一些非传统的事情来解决这个问题。 - staticsan
6
如果您这样做的话,可能会更加清晰易读:while (read_string(s) && s.len() > 5)。显然,如果read_string没有返回值(或者返回值是没有意义的),那么这种方法就行不通了。(编辑:对不起,我没有注意到这篇文章是多久以前的。) - jamesdlin
15
不要害怕在代码体中使用while (1)break;语句。试图强制将代码的退出部分移到while测试中或者移到do-while测试中,通常是浪费精力且使代码难以理解的做法。 - potrzebie
9
人们仍然在阅读它。如果你有有用的话要说,那就说出来。论坛中经常出现的问题是因为帖子通常按照最后一次发帖的日期排序。而StackOverflow没有这样的问题。 - Dimitar Slavchev
4
我更喜欢逗号的方式,而不是使用 while(1)break - Michael
显示剩余7条评论

50

逗号操作符将评估左操作数,丢弃结果,然后评估右操作数,这将是结果。如链接中所述的成语用法是在初始化for循环中使用的变量,并给出以下示例:

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

否则,逗号运算符的用法并不多,尽管很容易被滥用来生成难以阅读和维护的代码。
草案C99标准中,语法如下:
expression:
  assignment-expression
  expression , assignment-expression

第二段落中说:

逗号运算符的左操作数被评估为void表达式;在其评估后有一个序列点。然后评估右操作数;结果具有其类型和值。

脚注97说:

逗号运算符不产生lvalue。

这意味着你不能对逗号运算符的结果进行赋值。

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

#include <stdio.h>

int main()
{
    int x, y ;

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

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

将会有以下输出:

1 4

30

逗号运算符将其两侧的表达式合并成一个表达式,并按从左到右的顺序对它们进行评估。右侧表达式的值作为整个表达式的返回值。

(表达式1,表达式2)就像{表达式1;表达式2;},但你可以在函数调用或赋值中使用expr2的结果。

它经常出现在for循环中,以此初始化或维护多个变量:

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 */
}
除此之外,我只在另一个情况下“愤怒地”使用过它,那是在将应该一起进行的两个操作包装成一个宏时。我们有一些代码将各种二进制值复制到字节缓冲区中以发送到网络,并且指针维护了我们已经到达的位置:
unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

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

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

当值为shortint类型时,我们采用以下方法:

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

后来我们读到,这不是真正有效的C代码,因为(short *)ptr不再是一个左值,不能被递增,但当时我们的编译器并不在意。为了解决这个问题,我们把表达式分成两部分:

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

然而,这种方法依赖于所有开发人员始终记得同时放置这两个语句。我们想要一个函数,可以传入输出指针、值和值的类型。由于这是 C 而不是带有模板的 C++,我们无法让函数接受任意类型,因此我们选择了宏:

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

通过使用逗号操作符,我们可以根据需要在表达式或语句中使用this:

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 行中的表达式应该只包含循环控制代码,而不包含其他额外的初始化或循环维护部分。


8

这会导致多个语句被评估,但只使用最后一个作为结果值(我认为是rvalue)。

因此...

int f() { return 7; }
int g() { return 8; }

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

应该导致x被设置为8。

是的。如果省略外部大括号,它将被设置为11。对于某些情况来说,这非常有趣,绝对值得编译器发出警告。 - sebkraemer

3

正如之前的回答所述,它会评估所有语句,但只使用最后一个语句作为表达式的值。个人认为它只在循环表达式中有用:

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

2

我认为它最有用的地方是在编写复杂循环时,你可以在表达式(通常是初始化表达式或循环表达式)中执行多个操作。例如:

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循环,这样初始化和循环中的多个表达式会更加明显。(而且我会内联初始化i1和i2,而不是声明然后初始化....等等)


我猜你的意思是 i1=0,i2=size-1。 - frankster

-2
我重新激活这个话题,只是为了回答@Rajesh和@JeffMercado的问题,因为我认为这是最受欢迎的搜索引擎之一。
例如,看下面的代码片段。
int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

它会打印
1 5

大多数答案都解释了i的情况。所有表达式按从左到右的顺序进行评估,但只有最后一个被赋给i( 表达式 )的结果是1`。

j的情况遵循不同的优先级规则,因为,具有最低的运算符优先级。根据这些规则,编译器看到的是assignment-expression, constant, constant ...。表达式再次按从左到右的顺序进行评估,并且它们的副作用保持可见,因此,j的结果是j = 5,因此为5

有趣的是,语言规范不允许使用int j = 5,4,3,2,1;。初始化程序期望一个assignment-expression,因此不允许直接使用,运算符。

希望这对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接