C 逗号运算符的用途

发布于 2024-08-08 05:55:17 字数 64 浏览 8 评论 0原文

您会看到它在 for 循环语句中使用,但它在任何地方都是合法的语法。您在其他地方发现了它的哪些用途(如果有的话)?

You see it used in for loop statements, but it's legal syntax anywhere. What uses have you found for it elsewhere, if any?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(20

不醒的梦 2024-08-15 05:55:17

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;

或 as

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 language (as well as C++) is historically a mix of two completely different programming styles, which one can refer to as "statement programming" and "expression programming". As you know, every procedural programming language normally supports such fundamental constructs as sequencing and branching (see Structured Programming). These fundamental constructs are present in C/C++ languages in two forms: one for statement programming, another for expression programming.

For example, when you write your program in terms of statements, you might use a sequence of statements separated by ;. When you want to do some branching, you use if statements. You can also use cycles and other kinds of control transfer statements.

In expression programming the same constructs are available to you as well. This is actually where , operator comes into play. Operator , is nothing else than a separator of sequential expressions in C, i.e. operator , in expression programming serves the same role as ; does in statement programming. Branching in expression programming is done through ?: operator and, alternatively, through short-circuit evaluation properties of && and || operators. (Expression programming has no cycles though. And to replace them with recursion you'd have to apply statement programming.)

For example, the following code

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

which is an example of traditional statement programming, can be re-written in terms of expression programming as

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

or as

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

or

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

or

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

Needless to say, in practice statement programming usually produces much more readable C/C++ code, so we normally use expression programming in very well measured and restricted amounts. But in many cases it comes handy. And the line between what is acceptable and what is not is to a large degree a matter of personal preference and the ability to recognize and read established idioms.

As an additional note: the very design of the language is obviously tailored towards statements. Statements can freely invoke expressions, but expressions can't invoke statements (aside from calling pre-defined functions). This situation is changed in a rather interesting way in GCC compiler, which supports so called "statement expressions" as an extension (symmetrical to "expression statements" in standard C). "Statement expressions" allow user to directly insert statement-based code into expressions, just like they can insert expression-based code into statements in standard C.

As another additional note: in C++ language functor-based programming plays an important role, which can be seen as another form of "expression programming". According to the current trends in C++ design, it might be considered preferable over traditional statement programming in many situations.

好菇凉咱不稀罕他 2024-08-15 05:55:17

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

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

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

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

I think generally C's comma is not a good style to use simply because it's so very very easy to miss - either by someone else trying to read/understand/fix your code, or you yourself a month down the line. Outside of variable declarations and for loops, of course, where it is idiomatic.

You can use it, for example, to pack multiple statements into a ternary operator (?:), ala:

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

but my gods, why?!? (I've seen it used in this way in real code, but don't have access to it to show unfortunately)

蓝梦月影 2024-08-15 05:55:17

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

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

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

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

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

Two killer comma operator features in C++:

a) Read from stream until specific string is encountered (helps to keep the code DRY):

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

b) Write complex code in constructor initializers:

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};
以酷 2024-08-15 05:55:17

我见过它在宏中使用,其中宏假装是一个函数并想要返回一个值,但需要先做一些其他工作。它总是很丑陋,而且常常看起来像一个危险的黑客行为。

简化示例:

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

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

I've seen it used in macros where the macro is pretending to be a function and wants to return a value but needs to do some other work first. It's always ugly and often looks like a dangerous hack though.

Simplified example:

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

Here B=SomeMacro(A) "returns" the result of Permute(A) and assigns it to "B".

-黛色若梦 2024-08-15 05:55:17

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

using namespace boost::assign;

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

The Boost Assignment library is a good example of overloading the comma operator in a useful, readable way. For example:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;
坠似风落 2024-08-15 05:55:17

我必须使用逗号来调试互斥锁,以便在锁开始等待之前放置一条消息。

我只能将日志消息放在派生锁构造函数的主体中,因此我必须在初始化列表中使用: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__ )
    {
    }

  ....

};

I had to use a comma to debug mutex locks to put a message before the lock starts to wait.

I could not but the log message in the body of the derived lock constructor, so I had to put it in the arguments of the base class constructor using : baseclass( ( log( "message" ) , actual_arg )) in the initialization list. Note the extra parenthesis.

Here is an extract of the classes :

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__ )
    {
    }

  ....

};
胡大本事 2024-08-15 05:55:17

从C标准来看:

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

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

请注意:

int a, b, c;

不是逗号运算符,而是声明符列表。

From the C standard:

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. (A comma operator does not yield an lvalue.)) 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.

In short it let you specify more than one expression where C expects only one. But in practice it's mostly used in for loops.

Note that:

int a, b, c;

is NOT the comma operator, it's a list of declarators.

旧时光的容颜 2024-08-15 05:55:17

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

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

但是看看 这个可怕的失败,确实是你的,因为当你过度使用时会发生什么。)

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

It is sometimes used in macros, such as debug macros like this:

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

(But look at this horrible failure, by yours truly, for what can happen when you overdo it.)

But unless you really need it, or you are sure that it makes the code more readable and maintainable, I would recommend against using the comma operator.

香草可樂 2024-08-15 05:55:17

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

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

You can overload it (as long as this question has a "C++" tag). I have seen some code, where overloaded comma was used for generating matrices. Or vectors, I don't remember exactly. Isn't it pretty (although a little confusing):

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

桃气十足 2024-08-15 05:55:17

在 for 循环之外,即使有可能有代码气味,我认为逗号运算符唯一有用的地方是作为删除的一部分:

 delete p, p = 0;

替代方案的唯一价值是您可以如果该操作位于两行,则意外地仅复制/粘贴该操作的一半。

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

Outside of a for loop, and even there is has can have an aroma of code smell, the only place I've seen as a good use for the comma operator is as part of a delete:

 delete p, p = 0;

The only value over the alternative is you can accidently copy/paste only half of this operation if it is on two lines.

I also like it because if you do it out of habit, you'll never forget the zero assignment. (Of course, why p isn't inside somekind of auto_ptr, smart_ptr, shared_ptr, etc wrapper is a different question.)

东京女 2024-08-15 05:55:17

鉴于@Nicolas Goy 对标准的引用,那么听起来您可以为循环编写一行代码,例如:

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

但是天哪,伙计,您真的想让您的 C 代码以这种方式更加晦涩难懂吗? ?

Given @Nicolas Goy's citation from the standard, then it sounds like you could write one-liner for loops like:

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

But good God, man, do you really want to make your C code more obscure in this way?

茶花眉 2024-08-15 05:55:17

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

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

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

It's very useful in adding some commentary into ASSERT macros:

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

Since most assert style macros will output the entire text of their argument, this adds an extra bit of useful information into the assertion.

鹊巢 2024-08-15 05:55:17

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

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 之前,但如果该函数需要很长时间才能运行,您可能需要如果没有必要,则避免执行此操作,并且如果除非 a!=1,否则不应执行该函数,那么这不是一个选项。另一种方法是在 IF 中嵌套一个额外的层。这实际上是我通常所做的,因为上面的代码有点神秘。但我时不时地用逗号的方式来做,因为嵌套也很神秘。

In general I avoid using the comma operator because it just makes code less readable. In almost all cases, it would be simpler and clearer to just make two statements. Like:

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

offers no clear advantage over:

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

The one place besides loops where I have used it it in if/else constructs, like:

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

You could put the function before the IF, but if the function takes a long time to run, you might want to avoid doing it if it's not necessary, and if the function should not be done unless a!=1, then that's not an option. The alternative is to nest the IF's an extra layer. That's actually what I usually do because the above code is a little cryptic. But I've done it the comma way now and then because nesting is also cryptic.

知足的幸福 2024-08-15 05:55:17

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

I often use it to run a static initializer function in some cpp files, to avoid lazy initalization problems with classic singletons:

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
}
南城追梦 2024-08-15 05:55:17

对我来说,C 语言中逗号真正有用的一个例子是使用它们来有条件地执行某些操作。

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

这相当于

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

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

循环也一样:

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

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

For me the one really useful case with commas in C is using them to perform something conditionally.

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

this is equivalent to

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

This is not about "typing less", it's just looks very clear sometimes.

Also loops are just like that:

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

Of course both can only be useful when the conditional part or executable part of the loop is quite small, two-three operations.

青衫儰鉨ミ守葔 2024-08-15 05:55:17

我唯一一次在 for 循环之外使用 , 运算符是在三元语句中执行赋值。那是很久以前的事了,所以我不记得确切的说法,但它是这样的:

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

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

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

The only time I have ever seen the , operator used outside a for loop was to perform an assingment in a ternary statement. It was a long time ago so I cannot remeber the exact statement but it was something like:

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

Obviously no sane person would write code like this, but the author was an evil genius who construct c statements based on the assembler code they generated, not readability. For instance he sometimes used loops instead of if statements because he preferred the assembler it generated.

His code was very fast but unmaintainable, I am glad I don't have to work with it any more.

会傲 2024-08-15 05:55:17

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

它减少了一些重复输入,但您必须小心它不会变得太难以阅读。

请在此处查看我的过长版本的答案

I've used it for a macro to "assign a value of any type to an output buffer pointed to by a char*, and then increment the pointer by the required number of bytes", like this:

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

Using the comma operator means the macro can be used in expressions or as statements as desired:

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

It reduced some repetitive typing but you do have to be careful it doesn't get too unreadable.

Please see my overly-long version of this answer here.

游魂 2024-08-15 05:55:17

It can be handy for "code golf":

Code Golf: Playing Cubes

The , in if(i>0)t=i,i=0; saves two characters.

大姐,你呐 2024-08-15 05:55:17

qemu 有一些在 for 循环的条件部分中使用逗号运算符的代码(请参阅 QTAILQ_FOREACH_SAFE 在 qemu-queue.h 中)。他们所做的归结为以下内容:

#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

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

  • 它避免了进行两次赋值,因此减少了代码不同步的机会,
  • 因为它使用 < code>&&,最后一次迭代后不会评估分配
  • 由于分配未评估,因此当队列中的下一个元素位于末尾时,它不会尝试取消引用队列中的下一个元素(在qemu 的代码,而不是上面的代码)。
  • 在循环内部,您可以访问当前和下一个元素

qemu has some code that uses the comma operator within the conditional portion of a for loop (see QTAILQ_FOREACH_SAFE in qemu-queue.h). What they did boils down to the following:

#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;
}

... with the following output:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

The first version of this loop has the following effects:

  • It avoids doing two assignments, so the chances of the code getting out of sync is reduced
  • Since it uses &&, the assignment is not evaluated after the last iteration
  • Since the assignment isn't evaluated, it won't try to de-reference the next element in the queue when it's at the end (in qemu's code, not the code above).
  • Inside the loop, you have access to the current and next element
╰ゝ天使的微笑 2024-08-15 05:55:17

在数组初始化中找到它:

在 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

Found it in array initialization:

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

When I initialize an array a[][]:

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

and then display the array elements.

I get:

11 89 0 0 0 
0 0 0 0 0
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文