C逗号运算符的使用

时间:2009-10-23 12:52:06

标签: c++ c comma-operator

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

20 个答案:

答案 0 :(得分:97)

C语言(以及C ++)在历史上是两种完全不同的编程风格的混合,可以称之为“语句编程”和“表达式编程”。如您所知,每种过程编程语言通常都支持排序分支等基本结构(请参阅Structured Programming)。这些基本结构以两种形式存在于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编译器中以一种相当有趣的方式改变,它支持所谓的"statement expressions"作为扩展(与标准C中的“表达式语句”对称)。 “语句表达式”允许用户直接将基于语句的代码插入到表达式中,就像它们可以将基于表达式的代码插入到标准C中的语句中一样。

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

答案 1 :(得分:30)

我认为一般来说C的逗号不是一个好用的风格,因为它非常容易错过 - 要么是其他人试图阅读/理解/修复你的代码,要么是你自己一个月下线。当然,在变量声明和for循环之外,它是惯用的。

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

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

但我的众神,为什么?!? (我已经看到它在实际代码中以这种方式使用,但不幸的是无法访问它)

答案 2 :(得分:21)

我已经看到它在宏中使用,宏假装是一个函数,想要返回一个值,但需要先做一些其他的工作。它总是很难看,虽然看起来像是一个危险的黑客。

简化示例:

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

此处B=SomeMacro(A)“返回”Permute(A)的结果并将其分配给“B”。

答案 3 :(得分:20)

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

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

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

b)在构造函数初始化器中编写复杂代码:

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

答案 4 :(得分:10)

我不得不使用逗号来调试互斥锁,以便在锁开始等待之前发出消息。

我不得不在派生锁构造函数体中的日志消息,所以我不得不把它放在基类构造函数的参数中使用: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__ )
    {
    }

  ....

};

答案 5 :(得分:10)

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

using namespace boost::assign;

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

答案 6 :(得分:8)

来自C标准:

  

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

简而言之,它允许您指定多个表达式,其中C只需要一个表达式。但实际上它主要用于循环。

请注意:

int a, b, c;

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

答案 7 :(得分:5)

有时会在宏中使用它,例如调试宏:

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

(但请真实地看看this horrible failure,了解当你过度时会发生什么。)

但除非你真的需要它,或者你确信它使代码更具可读性和可维护性,我建议不要使用逗号运算符。

答案 8 :(得分:5)

你可以重载它(只要这个问题有一个“C ++”标签)。我看过一些代码,其中重载的逗号用于生成矩阵。或矢量,我不记得确切。不是很漂亮(虽然有点令人困惑):

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

答案 9 :(得分:5)

在for循环之外,甚至可以有代码味道的香气,我看到的唯一一个用于逗号运算符的地方是删除的一部分:

 delete p, p = 0;

替代方案的唯一值是,如果它在两行上,你可能会意外地复制/粘贴此操作的一半。

我也喜欢它,因为如果你出于习惯,你永远不会忘记归零。 (当然,为什么p不在某些auto_ptr,smart_ptr,shared_ptr等包装内部是一个不同的问题。)

答案 10 :(得分:4)

鉴于@Nicolas Goy对该标准的引用,听起来你可以为这样的循环编写单行代码:

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

但是天哪,伙计,你真的想以这种方式让你的C代码更多模糊吗?

答案 11 :(得分:3)

一般情况下,我避免使用逗号运算符,因为它只会降低代码的可读性。几乎在所有情况下,只做两个陈述会更简单,更清晰。像:

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嵌套为额外的层。这实际上是我通常所做的,因为上面的代码有点神秘。但我现在用逗号方式做到了这一点,因为嵌套也很神秘。

答案 12 :(得分:3)

ASSERT宏中添加一些评论非常有用:

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

由于大多数断言样式宏将输出其参数的整个文本,因此在断言中添加了一些额外的有用信息。

答案 13 :(得分:1)

我经常用它在一些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
}

答案 14 :(得分:1)

对我而言,C中逗号的一个非常有用的案例是使用它们来有条件地执行某些操作。

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

这相当于

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

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

循环也就是这样:

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

当然,只有当循环的条件部分或可执行部分非常小,两到三次操作时,两者才有用。

答案 15 :(得分:0)

我已经将它用于宏“将任何类型的值赋给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));

它减少了一些重复的打字,但你必须要小心它不会太难以理解。

请参阅此答案的过长版本here

答案 16 :(得分:0)

我唯一一次见过在,循环之外使用的for运算符就是在三元语句中执行辅助。这是很久以前所以我不能记住确切的陈述,但它是这样的:

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

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

他的代码非常快但不可维护,我很高兴我不再需要使用它了。

答案 17 :(得分:0)

“代码高尔夫”非常方便:

Code Golf: Playing Cubes

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

答案 18 :(得分: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的代码中,而不是上面的代码)。
  • 在循环内部,您可以访问当前和下一个元素

答案 19 :(得分:0)

在数组初始化中找到它:

In C what exactly happens if i use () to initialize a double dimension array instead of the {}?

初始化数组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