C逗号运算符的用法

你看它用于循环语句,但它是合法的语法在任何地方。 你在其他地方找到了什么用途,如果有的话?

C语言(以及C ++)在历史上是两种完全不同的编程风格的组合,可以称为“语句编程”和“表达式编程”。 如你所知,每一种程序编程语言通常都支持排序分支等基本结构。 这些基本结构以两种形式存在于C / C ++语言中:一种用于语句编程,另一种用于表达式编程。

例如,当你用语句来编写程序时,你可能会使用一系列由…分隔的语句; 。 当你想做一些分支,你使用if语句。 您也可以使用循环和其他类型的控制传输语句。

在表达式编程中也可以使用相同的结构。 实际上,这是操作员进场的地方。 运算符,除了C中的顺序表达式的分隔符,即运算符,在表达式编程中起到相同的作用; 在语句编程中。 表达式编程中的分支是通过?:操作符完成的,或者通过&&||短路评估属性来完成 运营商。 (表达式编程没有任何循环,要用递归替换它们,你必须使用语句编程。)

例如下面的代码

 a = rand(); ++a; b = rand(); c = a + b / 2; if (a < c - 5) d = a; else d = b; 

这是传统语句编程的一个例子,可以用表达式编程来重新编写

 a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b; 

或如

 a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b; 

要么

 d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b); 

要么

 a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b); 

不用说,在实践中,语句编程通常会产生更多可读的C / C ++代码,所以我们通常使用表达式编程来衡量和限制数量。 但在许多情况下,它来得方便。 可接受与不可接受之间的界限在很大程度上是个人偏好的问题,也是识别和阅读已有成语的能力。

作为补充说明:语言的设计明显是针对语句的。 语句可以自由地调用表达式,但表达式不能调用语句(除了调用预定义的函数外)。 这种情况在GCC编译器中有一个相当有趣的方式,它支持所谓的“语句表达式”作为扩展(与标准C中的“表达式语句”对称)。 “语句表达式”允许用户直接将基于语句的代码插入到表达式中,就像它们可以将基于表达式的代码插入到标准C中的语句中一样。

另外需要注意的是:在C ++语言中,基于函数的编程起着重要的作用,可以看作另一种形式的“表达式编程”。 根据目前C ++设计的趋势,在许多情况下,它可能被认为比传统的语句编程更受欢迎。

我认为一般来说C的逗号并不是一个很好的使用方式,因为它很容易错过 – 要么是别人试图读/理解/修正你的代码,要么是你自己一个月的时间。 变量声明和循环之外,当然,它是地方性的。

例如,您可以使用它将多个语句打包成三元运算符(?:),ala:

 int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117; 

但我的上帝,为什么?!? (我已经看到它在真实代码中以这种方式使用,但不能访问它以显示不幸)

我已经看到它在宏中使用宏假装是一个函数,并希望返回一个值,但需要先做一些其他的工作。 它总是丑陋,而且往往看起来像一个危险的黑客。

简单的例子:

 #define SomeMacro(A) ( DoWork(A), Permute(A) ) 

这里B=SomeMacro(A) “返回” B=SomeMacro(A)的结果并将其赋值给“B”。

C ++中的两个杀手逗号运算符功能:

a)从流中读取,直到遇到特定的字符串(有助于保持代码干燥):

  while (cin >> str, str != "STOP") { //process str } 

b)在构造函数初始值设定项中编写复杂的代码:

 class X : public A { X() : A( (global_function(), global_result) ) {}; }; 

Boost Assignment库是以有用可读的方式重载逗号运算符的一个很好的例子。 例如:

 using namespace boost::assign; vector<int> v; v += 1,2,3,4,5,6,7,8,9; 

我必须使用逗号来调试互斥锁才能在锁开始等待之前发出消息。

我不得不在派生的锁构造函数的主体中的日志消息,所以我不得不把它放在初始化列表中使用baseclass((log(“message”),actual_arg))的基类构造函数的参数。 请注意额外的括号。

这里是类的摘录:

 class NamedMutex : public boost::timed_mutex { public: ... private: std::string name_ ; }; void log( NamedMutex & ref__ , std::string const& name__ ) { LOG( name__ << " waits for " << ref__.name_ ); } class NamedUniqueLock : public boost::unique_lock< NamedMutex > { public: NamedUniqueLock::NamedUniqueLock( NamedMutex & ref__ , std::string const& name__ , size_t const& nbmilliseconds ) : boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) , boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ), ref_( ref__ ), name_( name__ ) { } .... }; 

从C标准:

逗号运算符的左操作数被评估为void表达式; 评估后有一个顺序点。 然后评估右操作数; 结果有它的类型和价值。 (逗号运算符不产生左值。))如果尝试修改逗号运算符的结果或在下一个序列点之后访问它,则行为是不确定的。

总之,它让你指定多个表达式,其中C只有一个。 但实际上它主要用于循环。

注意:

 int a, b, c; 

不是逗号运算符,它是一个声明的列表。

它有时用在宏中,比如像这样的调试宏:

 #define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size))) 

(但是请真诚地看待这个可怕的失败 ,因为当你过度的时候会发生什么。)

但是除非你真的需要它,否则你肯定会让代码更具可读性和可维护性,所以我建议不要使用逗号运算符。

你可以重载它(只要这个问题有一个“C ++”标签)。 我已经看到一些代码,重载逗号被用来生成矩阵。 或矢量,我不记得确切。 是不是很漂亮(虽然有点混乱):

MyVector foo = 2,3,4,5,6;

除了for循环之外,甚至还有可以产生代码气味的香气,我唯一看到的用作逗号运算符的地方是作为删除的一部分:

  delete p, p = 0; 

唯一的价值是你可以不经意地复制/粘贴这个操作的一半,如果它在两行上。

我也喜欢它,因为如果你习惯于这样做,你永远不会忘记零分配。 (当然,为什么p不在auto_ptr,smart_ptr,shared_ptr等等包装器中是另外一个问题。)

鉴于@尼古拉斯·戈伊的标准引用,那么这听起来像你可以写一个循环如下:

 int a, b, c; for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--); printf("%d", c); 

但是上帝啊,真的,你真的想用这种方式让你的C代码更加模糊吗?

ASSERT宏中添加一些注释非常有用:

 ASSERT(("This value must be true.", x)); 

由于大多数断言样式宏将输出他们的参数的整个文本,这增加了一些额外的有用的信息到断言。

一般来说,我避免使用逗号运算符,因为它只是使代码更不可读。 在几乎所有情况下,只要做两个陈述就会更简单和更清楚。 喜欢:

 foo=bar*2, plugh=hoo+7; 

没有明显的优势:

 foo=bar*2; plugh=hoo+7; 

除了循环之外,我在if / else结构中使用它的地方,如:

 if (a==1) ... do something ... else if (function_with_side_effects_including_setting_b(), b==2) ... do something that relies on the side effects ... 

你可以把这个函数放在IF之前,但是如果这个函数需要很长时间才能运行,那么如果没有必要,你可能想要避免这样做,而且如果这个函数不应该被执行,除非!= 1,那么这不是选项。 另一种方法是嵌套IF的额外层。 这实际上是我通常做的,因为上面的代码有点神秘。 但是我现在用逗号的方式做了,因为嵌套也是神秘的。

我经常用它来在一些cpp文件中运行一个静态的初始化函数,以避免经典单例的懒惰初始化问题:

 void* s_static_pointer = 0; void init() { configureLib(); s_static_pointer = calculateFancyStuff(x,y,z); regptr(s_static_pointer); } bool s_init = init(), true; // just run init() before anything else Foo::Foo() { s_static_pointer->doStuff(); // works properly } 

对我来说,C语言中一个非常有用的例子就是用它们来有条件地执行某些事情。

  if (something) dothis(), dothat(), x++; 

这相当于

  if (something) { dothis(); dothat(); x++; } 

这不是“打字少”,有时看起来很清楚。

循环也是这样的:

 while(true) x++, y += 5; 

当然,这两个方法只有在循环的条件部分或可执行部分非常小,二三个操作时才有用。

我唯一见过的,for循环之外使用的操作符是在三元语句中执行的。 这是很久以前,所以我不能记住确切的说法,但它是这样的:

 int ans = isRunning() ? total += 10, newAnswer(total) : 0; 

显然没有一个理智的人会这样写代码,但是作者是一个邪恶的天才,他们根据他们生成的汇编代码来构造c语句,而不是可读性。 例如,他有时使用循环而不是if语句,因为他更喜欢它生成的汇编程序。

他的代码非常快,但是无法维护,我很高兴我不必再使用它了。

我已经使用它为一个宏来“分配任何类型的值到一个char *所指向的输出缓冲区,然后增加指针所需的字节数”,如下所示:

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

使用逗号运算符意味着可以根据需要在表达式或语句中使用宏:

 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)); 

它减少了一些重复键入,但你必须小心,它不会太难以理解。

请在这里看到我这个答案的超长版本。

它可以方便的“代码高尔夫”:

代码高尔夫:玩立方体

if(i>0)t=i,i=0; 保存两个字符。

qemu有一些在for循环的条件部分使用逗号运算符的代码(参见qemu-queue.h中的QTAILQ_FOREACH_SAFE)。 他们做了什么归结为以下几点:

 #include <stdio.h> int main( int argc, char* argv[] ){ int x = 0, y = 0; for( x = 0; x < 3 && (y = x+1,1); x = y ){ printf( "%d, %d\n", x, y ); } printf( "\n%d, %d\n\n", x, y ); for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){ printf( "%d, %d\n", x, y ); } printf( "\n%d, %d\n", x, y ); return 0; } 

…具有以下输出:

 0, 1 1, 2 2, 3 3, 3 0, 1 1, 2 2, 3 3, 4 

这个循环的第一个版本有以下效果:

  • 它避免了做两个任务,所以代码不同步的机会减少了
  • 由于它使用&& ,因此在最后一次迭代之后不对赋值进行评估
  • 由于赋值没有被评估,它不会试图在队列末尾(在qemu的代码中,而不是上面的代码)中去引用队列中的下一个元素。
  • 在循环内部,您可以访问当前和下一个元素

在数组初始化中发现它:

在C中究竟发生了什么,如果我使用()来初始化一个二维数组而不是{}?

当我初始化一个数组a[][]

 int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)}; 

然后显示数组元素。

我得到:

 11 89 0 0 0 0 0 0 0 0