在条件语句中使用逗号的优点是什么?

60

我们可以将条件语句写成if语句的形式

if (a == 5, b == 6, ... , thisMustBeTrue)

只有最后一个条件满足才能进入if代码块。

为什么允许这样做?


3
你可以执行命令,例如检查某些内容或初始化。但是我承认,我不会使用它,因为它不易读懂。在for循环中,增加/减少多个变量更为常见。 - usr1234567
8
这取决于变量 ab 的类型。对于用户定义的类型,可以重载 operator== 并具有副作用。这种副作用可能是向您的银行账户存入一定金额的钱。考虑到这一点,您可以考虑多次调用该运算符具有优势。 - juanchopanza
1
@RSahu 这个不会导致未定义行为。 - M.M
16
如果你想通过操作符重载来实现混淆,那么你也可以重载operator,()以获得额外的乐趣! - rodrigo
1
@sharptooth:你的意思是,一个应用程序是为了解决某个编译器长期存在的错误。;-) - DevSolar
显示剩余10条评论
7个回答

69

稍微修改一下你的例子,假设它是这样的

if ( a = f(5), b = f(6), ... , thisMustBeTrue(a, b) )

(请注意,此处使用=而不是==)。在这种情况下,逗号确保从左到右进行评估。相比之下,使用以下内容
if ( thisMustBeTrue(f(5), f(6)) )

您不知道f(5)是在f(6)之前还是之后被调用。

更正式地说,逗号允许您以相同的方式编写表达式序列(a,b,c),就像您可以使用;来编写语句序列a; b; c;一样。 正如;创建序列点(完整表达式的结尾)一样,逗号也是如此。只有序列点控制求值顺序,请参见this post

但当然,在这种情况下,您实际上会写成这样

a = f(5);
b = f(6);    
if ( thisMustBeTrue(a, b) )

那么,什么情况下逗号分隔的表达式序列比分号分隔的语句序列更可取?我想几乎从不。也许在宏中,当您希望右侧替换为单个表达式时。


1
不错的例子,但我更愿意使用while循环头进行演示。对于“if”情况,我看不出为什么我不能在if语句前面直接编写这些语句的原因。 - MikeMB

45

简而言之: 虽然在 if 或者 while 语句的条件部分使用逗号运算符是合法的,但通常并没有意义(编辑:虽然后者有时可能会有帮助,正如 user5534870 在他的答案中所解释的那样)。

更详细的解释: 除了其语法功能(例如在初始化列表、变量声明或函数调用/声明中分隔元素),在 C 和 C++ 中,, 还可以像 + 等一样成为普通的运算符,因此可以在允许表达式的任何地方使用它(在 C++ 中甚至可以重载它)。
与大多数其他运算符不同的是,尽管两侧都会被求值,但它不会以任何方式组合左右表达式的输出,而只是返回右表达式的值。
它的引入,是因为某个人(可能是 Dennis Ritchie)认为 C 需要一种语法,在一个位置写入两个(或更多)不相关的表达式,而你通常只能写一个单独的表达式。

现在,if 语句的条件部分是(除其他外)这样的一个位置,因此你也可以在那里使用 , 运算符——是否有意义使用它是一个完全不同的问题!特别是——与函数调用或变量声明不同——逗号在那里没有特殊的意义,因此它会评估左右表达式,但只返回右边的结果,然后由 if 语句使用。

我现在能想到只有两个点,在这两个点上使用(未重载的,-运算符是有意义的:

  1. 如果你想在 for 循环头中同时递增多个迭代器:

    for ( ... ; ... ; ++i1, ++i2){
        *i2=*i1;
    }
    
  2. 如果你想在C++11的constexpr函数中评估多个表达式。

再重申一遍:在if或while语句中使用逗号运算符——就像你在例子中展示的那样——并不是什么明智的做法。这只是C和C ++语言语法允许您编写代码的另一个例子,该代码的行为方式与人们第一眼看到它时所期望的不同。还有很多其他例子....


1
@AbdulRehman:那么答案可能是:因为if和while语句需要一个计算结果为true的表达式,而(<ex1>,<ex2>)就是一个表达式。 - MikeMB
12
@Abdul:好的,因为你必须选择一个条件,并且有人决定它应该是最后一个条件。你必须明白,在if语句中使用逗号运算符与在代码的其他位置使用它没有任何特殊行为差别。 - MikeMB
你能为我澄清一下吗?在constexpr中使用逗号表达式是否有理由,而不能简单地使用2+ constexprs代替呢? - BeyelerStudios
@Abdul Rehman:逗号运算符与标点符号中的逗号是不同的。 - user1084944
1
decltype 中,你可以将其用作简单的 SFINAE。 decltype(test_expression, void(), result_we_want) 将测试第一个表达式(确定它是否有效),如果它是有效的,则返回所需的 result_we_want 类型。参数包展开技巧 -- using discard=int[]; (void)discard{ 0, ((std::cout << ts << '\n'), void(), 0)... }; 将为 ts... 参数包中的每个元素(按顺序)展开语句 std::cout << ts << '\n' 并且丢弃结果。(在这种情况下,并非所有 , 都是逗号运算符,但是有些是) - Yakk - Adam Nevraumont
显示剩余18条评论

15

对于一个 if语句,把某些东西放到逗号表达式里面而不是外面没有实际意义。

对于一个 while语句,将逗号表达式放到条件中会执行第一部分,要么在进入循环时执行,要么在循环时执行。 这就不能轻易地通过代码复制来复制。

那么 do...while语句怎么样呢? 在这里我们只需要担心循环本身,对吧? 事实证明,在这里甚至也不能安全地用逗号表达式替换移动第一部分进入循环体中。

首先,循环体中变量的析构函数此时还没有运行过,这可能会产生影响。 另外,任何在循环内的 continue 语句仅当它确实在条件中而不是在循环体中时,才会达到逗号表达式的第一部分。


3
也许您可以添加一些代码示例?目前来看,这太令人困惑了。 - Walter

10

没有任何优势:逗号操作符只是一个具有其表达式列表中最后一个表达式类型的表达式,并且if语句评估布尔表达式。

if(<expr>) { ... }
 with type of <expr> boolean

这是一个奇怪的操作符,但没有什么魔法 - 除了它把表达式列表和函数调用中的参数列表混淆了。

foo(<args>)
 with <args> := [<expr>[, <expr>]*]

请注意,在参数列表中,逗号对于分隔参数具有更强的绑定力。


他们可以在C++的If语句语法中使用赋值表达式,而不是包含表达式列表。 - Abdul Rehman
是的,但它与C++语法中的表达式不同,表达式是一系列赋值表达式,而赋值表达式是不包含逗号的表达式。 - Abdul Rehman
3
我认为我们都同意在for循环中,逗号表达式是不必要的 ;) 对于初始化和增量的语法,可以将其简单地作为语句列表而不是表达式,这样就不会有混淆了... - BeyelerStudios
@BeyelerStudios,你能否帮我澄清一下,如果我们重载了某些关系运算符或相等运算符,是否有任何优势? - Abdul Rehman
@AbdulRehman 当然:想象一下实现<complex>或任何其他数学库。 - BeyelerStudios
显示剩余4条评论

7
以下内容有点牵强,具体取决于您想要多么狡猾。
考虑这样一种情况:一个函数通过修改引用传递或指针传递的参数来返回值(也许是由于库设计不良,或者为了确保该值不被忽略而在返回后没有被分配)。
void calculateValue(FooType &result) {/*...*/}

那么,如何使用依赖于result的条件语句呢?您可以声明将被修改的变量,然后使用if进行检查:
FooType result;
calculateValue(result);
if (result.isBared()) {
    //...
}

这可以简化为:
FooType result;
if (calculateValue(result) , result.isBared()) {
    //...
}

这并不是很值得。然而,对于while循环来说,可能会有一些小优势。如果需要/可以调用calculateValue直到结果不再被bar'd,我们将得到如下内容:

FooType result;
calculateValue(result);  //[1] Duplicated code, see [2]
while (result.isBared()) {
    //... possibly many lines
    //separating the two places where result is modified and tested

    //How do you prevent someone coming after you and adds a `continue`
    //here which prevents result to be updated in the and of the loop?

    calculateValue(result); //[2] Duplicated code, see [1]
}

并可以简化为:

FooType result;
while (calculateValue(result) , result.isBared()) {
    //all your (possibly numerous) code lines go here
}

这种方法可以将更新result的代码放在一个地方,并且靠近检查条件的代码行。

也许与此无关:变量通过参数传递进行更新的另一个原因是函数需要返回错误代码以及修改/返回计算出的值。在这种情况下:

ErrorType fallibleCalculation(FooType &result) {/*...*/}

那么

FooType result;
ErrorType error;

while (error = fallibleCalculation(result) , (Success==error && result.isBared())) {
    //...
}

但是如评论中所述,您也可以不使用逗号来实现这一点:

FooType result;
ErrorType error;

while (Success == fallibleCalculation(result) && result.isBared()) {
    //...
}

1
@AbdulRehman:是的,代码重复是不好的。特别是while循环末尾的代码与其目的无关,你很可能会忘记在某些情况下更新它,而第一个实例被更新时也同样如此。请注意,“断开连接”的论点是for(..;..;..)子句中第三个(增量)组件存在的唯一原因,我从未听说过有人抱怨那个是无用的。 - Marc van Leeuwen
当(成功 == fallibleCalculation(result) && result.isBared())时,使用while循环。对于void函数(在这种情况下是不良的API),请使用for循环来抵消while的断开参数。 - BeyelerStudios
请考虑使用lambda。 - Yakk - Adam Nevraumont
@BeyelerStudios 你会如何使用for语句?你仍然需要使用类似for (calc(result) ; result.isBared() ; calc(result)) {...}这样的东西,或者你有其他想法吗? - frozenkoi
@frozenkoi 不,就这样。 - BeyelerStudios

4

没有任何意义。该代码中对a的比较是完全多余的。


7
我不确定,可能会有一个void operator==(int rhs){std :: cout << rhs;}。这是一个人们在if语句中使用“,”的代码库,我不能排除这种情况。 - Yakk - Adam Nevraumont

2
我的问题是在 ifwhile 语句中使用逗号的优势是什么?为什么允许这样做?
这是因为在 C 中,语句和表达式是不同的东西。一个复合的表达式是一种从理论上理解的结构(以及其他一些语言),如果没有以逗号的形式添加它,就会缺失。其在 for 语句中的使用是添加它们所需的原始理由。但是,通过从理论上使语言更完整,后来发现了没有人计划使用的用途。早期的 C++ 是生成 C 代码的翻译器,具有顺序表达式对于允许内联函数真正生成 C 代码中的“在线”逻辑是绝对必要的。这包括表达式出现的任何地方,包括 if 语句的条件。同样,在“有趣”的宏中也使用了它。尽管 C++ 通过提供内联函数消除了宏,但最新的编译器直到 x11 都发现 Boost FOREACH 范围循环(最终是一种模拟在 x11 中添加到语言中的功能)非常方便,并且那是一组具有魔力的巧妙宏,涉及逗号运算符。(嗯,当前版本通过链式if/else将其扩展为多个语句,而不是将其全部塞入单个 while 中。)现在,还有另一种方法可以将任何语句放入表达式中(lambda),因此未来模拟甚至新的语言特性或特定于领域的嵌入式语言的疯狂业务可能不需要再使用它了。
所以,不要编写那样的代码。除非比编写辅助函数或分成多个语句更清晰且确实更简单。但是,对于您想要轻松在一个地方使用的宏,而那个位置位于ifwhile 的括号内,这可能正是合适的。在 C++ 源代码中托管的特定于领域的语言,或者是语言仿真特性,例如(也许)在嵌入式实时系统中使用的替代异常处理的方法,这可以得到证明。简而言之,并没有正常的好用法。但是为了完整性,它还是存在的,您永远不知道什么时候会有人发现它有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接