,操作符在c中做什么?
- 逗号运算符的正确用法可能是什么?
- 正如我在回答中指出的,左操作数的计算后有一个序列点。这与函数调用中的逗号不同,后者只是语法上的。
- @谢尔盖。-考虑到这一问题比另一个问题早了几年被问和回答,另一个问题更有可能是这个问题的副本。然而,另一个也是双重标记的C和C++,这是一个讨厌的东西。这是一个只有C-only的问答,回答得体。
表达式:
1
| (expression1, expression2) |
首先计算表达式1,然后计算表达式2,并为整个表达式返回表达式2的值。
- 如果我写i=(5,4,3,2,1,0),那么理想情况下它应该返回0,对吗?但是我被分配了5个值?你能帮我明白我哪里出了问题吗?
- @james:逗号操作的值总是最后一个表达式的值。在任何情况下,i都不具有值5、4、3、2或1。它只是0。除非表达式有副作用,否则它实际上是无用的。
- 请注意,逗号表达式的lhs评估和rhs评估之间有一个完整的序列点(参见Shafik Yaghmour的答案,获取c99标准的引用)。这是逗号运算符的一个重要属性。
- 查看docs.microsoft.com/en-us/cpp/cpp/comma-operator,如果不使用圆括号,似乎会有区别。如果i=b,c;b被分配给i。这是怎么回事?在函数参数的情况下,括号的用法是必须的,微软对此进行了明确的解释。
- i = b, c;相当于(i = b), c,因为赋值=的优先级高于逗号运算符,。逗号运算符的优先级最低。
- 我担心括号会误导两个方面:(1)它们不是必需的-逗号运算符不必被括号包围;(2)它们可能与函数调用的参数列表周围的括号混淆-但参数列表中的逗号不是逗号运算符。然而,修复它并不完全是琐碎的。可能:在语句中:expression1, expression2;首先对expression1进行评估,可能是因为它的副作用(比如调用函数),然后有一个序列点,然后对expression2进行评估,返回值…
我见过在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);
} |
。
- 嘿,真漂亮!我经常不得不做一些非正统的事情来解决这个问题。
- 如果你做一些类似的事情:while (read_string(s) && s.len() > 5),它可能不那么晦涩,更可读。显然,如果read_string没有返回值(或者没有有意义的返回值),这就不起作用。(编辑:对不起,没注意到这篇文章有多旧。)
- @Staticsan不怕在体内使用while (1)和break;语句。试图将代码的中断部分强制到while测试中,或者强制到do-while测试中,这通常是浪费精力,使代码更难理解。
- @詹姆斯德林…人们仍然在读它。如果你有什么有用的话,那就说出来。论坛对恢复的线程有问题,因为线程通常按最后一篇文章的日期排序。StackOverflow没有这样的问题。
- @我更喜欢逗号方法,比while(1)和break好得多;
- 是的,这是一个非常好的干*和理由很少使用的操作员。(*第二个例子非常冒犯!)
逗号运算符将计算左操作数,放弃结果,然后计算右操作数,这就是结果。链接中提到的惯用用法是在初始化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 ) ;
} |
将具有以下输出:
。
逗号运算符将它两边的两个表达式组合为一个表达式,按从左到右的顺序对它们进行计算。右侧的值作为整个表达式的值返回。(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回路维护。
- 谢谢!这是我在stackoverflow上的第一个答案:从那时起,我也许已经了解到简洁性是值得重视的:—)。
- 有时候,我看重一点冗长的内容,正如这里您描述解决方案的演进(您是如何做到这一点的)。
它导致对多个语句进行评估,但只使用最后一个语句作为结果值(我认为是右值)。
所以…
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", (0,i ++), (0,i ++)); |
现在,在对i++的每个评估之间都有一个序列点,因此至少程序不会再冒崩溃和烧坏的风险,即使函数参数的评估顺序仍未明确。
当然,没有人会在实际的应用程序中编写这样的代码,它只对语言律师讨论C语言中的序列点有用。
逗号运算符被misra-c:2004和misra-c:2012禁止,理由是它创建的代码可读性较低。
- 您的"未指定"版本仍然未定义。参数的计算可能重叠,因此首先计算两个0,然后有一个序列点,最后计算两个增量。这满足了逗号lhs和rhs之间存在序列点的要求。对于重写的for循环,考虑continue。你所建议的是等价的,实际上不是。至于可读性,考虑src++, dst++,在代码中保持两个增量在一起,使读者更容易验证两个指针的增量是否完全相同。
- @我不认为一个严格符合的实现可以删除序列点,这是你的评论所建议的。为什么有人会考虑继续?它只对意大利面编码有用(也被misra禁止)。不管怎样,这个循环在结果和C的"抽象机"方面都是等价的。我真的不认为src++, dst++;比src++; (newline) dst++;更可读。我怀疑很少有人这样做。
- 好,然后计算0,计算0,序列点,序列点,计算i++,计算i++。同样的结果,所以我认为区别并不重要。至于src++,dst++,我指的是出现在for中的时间,因为这就是您的答案关注的地方。
- 关于continue,非misra代码可以并且确实使用它,所以它不能立即被丢弃。但我的观点是,在重写的版本中,continue将跳过count++。
- @hvd,不管怎样,逗号操作符是否在实际情况下修复了ub并不重要,因为我的示例只是在这里将关于序列点的讨论命名为逗号操作符的一种潜在用法。
- "但是上面的代码可以用一种更可读的方式编写,而不需要逗号操作符"。这是一种意见,我完全不同意。
- @当你给迭代器取更长的名字或者混合两个以上的名字时,这就变得相当明显了。for(int longername=0, alsolongname=0, anotherlongname=0; longername < something; longername++, alsolongname++, anotherlongname++)。这并不是说这段代码是一个不可读的混乱,因为它是显而易见的。换句话说,当表达式变得更复杂时,逗号运算符样式的伸缩性非常差。
- 这在很大程度上是一种意见。并非所有变量名都有15个字符长(strawman),并且在头中声明了许多有意义的构造(例如,当一个集合中有两个索引以不同的方式遍历时)。
- @如果你的表达是如此复杂以至于难以判断评估顺序,那么正确的方法是将它分成几行。
- 逗号运算符在生成代码但不需要新作用域的宏中非常有用。
- @Samliddicott和"生成代码的宏"在大多数时候都是不好的做法。对这些东西的需求往往源于糟糕的设计。
- @伦丁:我同意这样的需求往往源于C语言的糟糕设计和C宏系统的糟糕设计。逗号运算符有助于超越这种糟糕的设计。通过明智地使用逗号运算符和递归宏调用,我已经生成了一些Linq eqsue宏来将结构化JSON嵌入到C中。我现在编写的是JSON的C-ISH表示,而不是长循环的C来用特定的库发出JSON。编写的代码更简单,许多类的bug变得不可能。我们中的一些人可以欣赏逗号运算符,而不必如此随意地忽略它。
- @Samliddicott没有一个具体的例子,关于你为什么认为你找到了逗号运算符的有效用法的争论是毫无意义的。我假设您有一个表达式需要返回一个值,但不能使用函数。有几种方法可以通过宏实现这一点,而不使用逗号运算符。
- 你改变了count的范围,这是一个新问题。你的第一个例子甚至没有意义,因为i和count做的完全一样。为什么不提供一个这样两个世界中最好的例子:for(int i=0, count=0; i,它实际上根本不使用逗号运算符,并且可读(至少在我看来);
- @幻影你的例子是可以使用的,只要它不使循环膨胀。它实际上不是逗号运算符,而是初始值设定项列表。
正如前面的答案所述,它计算所有语句,但使用最后一个语句作为表达式的值。我个人认为它只在循环表达式中有用:
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,而不是声明然后初始化….等等。)
- 我想你的意思是I1=0,I2=1号
- 的确!适当更正。
我只是想回答@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 ); |
。
它将打印
i案件的处理与大多数答案一样。所有表达式都按从左到右的顺序计算,但只有最后一个表达式被分配给i。(表达的结果)is1`。
由于,具有最低的运算符优先级,因此j大小写遵循不同的优先级规则。由于这些规则,编译器可以看到赋值表达式、常量、常量……表达式按从左到右的顺序重新计算,其副作用保持可见,因此,由于j = 5,j是5。
有趣的是,语言规范不允许使用int j = 5,4,3,2,1;。初始值设定项需要赋值表达式,因此不允许使用直接,运算符。
希望这有帮助。