你看到它用于for循环语句,但它在任何地方都是合法的语法.您在其他地方找到了什么用途,如果有的话?
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的逗号不是一个好用的风格,因为它非常容易被错过 - 要么是其他人试图阅读/理解/修复你的代码,要么是你自己一个月下来.当然,在变量声明和for循环之外,它是惯用的.
例如,您可以使用它将多个语句打包成三元运算符(?:),ala:
int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;
但是我的天哪,为什么?!?(我已经看到它在实际代码中以这种方式使用,但不幸的是无法访问它)
我已经看到它在宏中使用,宏假装是一个函数,想要返回一个值,但需要先做一些其他的工作.它总是丑陋的,虽然看起来像是一个危险的黑客.
简化示例:
#define SomeMacro(A) ( DoWork(A), Permute(A) )
这里B=SomeMacro(A)
"返回"Permute(A)的结果并将其分配给"B".
C++中的两个杀手逗号运算符功能:
a)从流中读取直到遇到特定字符串(有助于保持代码DRY):
while (cin >> str, str != "STOP") { //process str }
b)在构造函数初始化器中编写复杂代码:
class X : public A { X() : A( (global_function(), global_result) ) {}; };
我必须使用逗号来调试互斥锁,以便在锁开始等待之前发送消息.
我不得不在派生锁构造函数体中的日志消息,所以我不得不在初始化列表中使用: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__ ) { } .... };
所述升压分配库是在一个有用的,可读的方式重载逗号操作符的一个很好的例子.例如:
using namespace boost::assign; vectorv; v += 1,2,3,4,5,6,7,8,9;
从C标准:
逗号运算符的左操作数被计算为void表达式; 评估后有一个序列点.然后评估右操作数; 结果有它的类型和价值.(逗号运算符不会产生左值.))如果尝试修改逗号运算符的结果或在下一个序列点之后访问它,则行为未定义.
简而言之,它允许您指定多个表达式,其中C只需要一个.但实际上它主要用于循环.
注意:
int a, b, c;
不是逗号运算符,它是一个声明符列表.
你可以重载它(只要这个问题有一个"C++"标签).我看过一些代码,其中重载的逗号用于生成矩阵.或矢量,我不记得确切.不是很漂亮(虽然有点令人困惑):
MyVector foo = 2,3,4,5,6;
它有时用在宏中,例如调试宏,如下所示:
#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))
(但是,真实地看看这个可怕的失败,因为当你过度时会发生什么.)
但除非你确实需要它,或者你确信它使代码更具可读性和可维护性,否则我建议不要使用逗号运算符.
在for循环之外,甚至可以有代码气味的香气,我看到的唯一一个用于逗号运算符的地方是删除的一部分:
delete p, p = 0;
替代方案的唯一值是,如果它在两行上,你可以意外地复制/粘贴这个操作的一半.
我也喜欢它,因为如果你出于习惯,你永远不会忘记归零.(当然,为什么p不在某些auto_ptr,smart_ptr,shared_ptr等包装器内部是一个不同的问题.)