【问题标题】:Blank or do-nothing 2nd or 3rd argument for ternary operator三元运算符的空白或无操作第二或第三个参数
【发布时间】:2013-09-11 05:01:46
【问题描述】:

是否可以为三元运算符提供一个空白的第二或第三个参数,或者有一个非上下文特定且意味着“什么都不做”的参数?在下面的示例中,我希望一个三元运算符将一个整数变量乘以 2,如果它是偶数,否则什么也不做。除了自赋值、加或减零或乘以或除以一之外,我想不出第三个参数的任何东西。无论如何,它们都是特定于上下文的。我想要对所有三元运算符都意味着“什么都不做”的东西。我尝试将参数留空,但无法编译。

#include <iostream>

int main()
{
    int x = 5;
    (x % 2 == 0) ? x *= 2 : x = x; // or x += 0, x -= 0, x *= 1, or x /= 1
    std::cout << x << std::endl;
    return 0;
}

是否有任何解决方案不涉及重述已经存在的变量、函数或对象?例如,考虑两个函数foogoo,其中foo 返回bool。假设由于某种原因,这两个函数都不能再次调用,例如后续调用中的状态更改。

int main()
{
    ( foo() ) ? goo() : ; // won't compile
    return 0;
}

【问题讨论】:

  • 这通常是这样写的:x = (x % 2 == 0) ? x * 2 : x;
  • 这可行,但它只是一种看起来很奇怪的方式:(x % 2 == 0) ? x *= 2 : 0;
  • 只需使用if。最好像这样:if (is_even(x)) x *= 2;(即:一个函数)。
  • 如果您正在寻找混淆,请继续您当前的道路。如果您希望人们即使一目了然 也能理解您的代码,请注意@GManNickG 的建议;使用if
  • 这里不需要三元运算符。 if (x % 2 == 0) x *= 2; 干净多了。

标签: c++ c++11 ternary-operator conditional-operator


【解决方案1】:

如果您要使用三元运算符,您可能希望分配其结果:

x = (x % 2 == 0) ? 2 * x : x;

就个人而言,我想我只需要一点点数学:

x <<= ((x & 1) ^ 1);

x&amp;1 给出x 的最低有效位,如果 x 为奇数则为 1,如果为偶数则为 0。 ^ 1 部分翻转该位,因此如果 x 为偶数,则为 1,如果 x 为奇数,则为 0。然后我们将x 左移那么多位。移位 0 位显然会使 x 保持不变。左移一位将x 乘以 2。

至于为什么后者会(或至少可能)更可取,这主要归结为一个问题,即您是否真的关心这种情况下的性能。如果您处于性能无关紧要的情况,那么if ((x%2)==0) x *= 2; 之类的内容可能是您的最佳选择。

我至少猜到了这个问题的至少部分原因是对效率的担忧。如果是这样,纯数学方法可能是更好的选择。例如,让我们考虑一下 VC++ 为这两者生成的代码:

; mathematical version:
mov eax, DWORD PTR _x$[esp-4]
mov ecx, eax
not ecx
and ecx, 1
shl eax, cl

[注意:对于此源代码,g++ 生成几乎相同的目标代码]。

忽略从内存中加载x 的(可能)时间,这应该在几乎任何回到 386 左右的英特尔处理器上执行不超过 4 个时钟周期。更好的是,我希望几乎任何任何处理器的编译器都可以产生非常相似的结果——对于几乎任何合理的处理器,从源代码到汇编语言的直接直译将在四条指令中进行实际的数学运算,每条指令都尽可能简单和快速.

使用if 语句的版本如下:

; Line 2
    mov ecx, DWORD PTR _x$[esp-4]
    mov eax, ecx
    and eax, -2147483647            ; 80000001H
    jns SHORT $LN5@f
    dec eax
    or  eax, -2                 ; fffffffeH
    inc eax
$LN5@f:
    lea eax, DWORD PTR [ecx+ecx]
    je  SHORT $LN1@f
; Line 3
    mov eax, ecx
$LN1@f:

随着编译的进行,这还不错。至少避免div 是实现%2 的明显方式。不幸的是,它仍然不够聪明,无法具有竞争力——它仍然有几个分支,其中一个可能不太可预测,所以大约有一半的时间我们会为一个错误预测的分支付出代价。

根据编译器的不同,您可以(并且将会)看到比这更好的。例如,改用 g++,我得到了这个:

    mov eax, DWORD PTR [ebp+8]
    and eax, 1
    test    eax, eax
    jne L2
    sal DWORD PTR [ebp+8]
L2:
    mov eax, DWORD PTR [ebp+8]

虽然在此代码上确实比 VC++ 更好,但它仍然远不及数学版本那么好。特别是,除非最低有效位是相当可预测的偶数或奇数,否则该分支很可能有一半的时间被错误预测。

底线:充其量,这可能接近于匹配数学版本——但这将取决于编译器和输入数据的合作。除了编译器和输入数据的最偶然组合之外,几乎可以肯定它至少会慢 2 倍,而 10 倍甚至不足为奇。

当然,根据我使用的标志、编译器版本等,我可能能够从任一编译器中获得比实际效果更好的结果。有了一些坚持,我可能甚至得到与代码的数学版本相同的结果。除非我非常了解目标编译器和 CPU,否则我什至不确定是否能获得同样好的结果——而且它们变得更好的可能性在我看来充其量是极其渺茫的。

【讨论】:

  • @DavidRodríguez-dribeas:见编辑后的答案。底线:从问题的语气来看,我猜(虽然可能是错误的)他关心表现。
  • @Jerry Coffin,你说得对——我关心性能。非常全面的答案!
【解决方案2】:
(x % 2 == 0) ? x *= 2 : x = x;

条件运算符不是 if 语句,它的意思是产生一个值,如果你不需要这个值,就像上面的情况,只是按原样输入if

if (x%2==0) x*=2;

这更易于阅读和维护,并且在所有其他方面都等效。如果您认为您的代码在任何方面都会更好,请重新考虑。

从问题中的评论看来,您想了解它是如何工作的,而不仅仅是编写看起来很奇怪的代码。如果是这样的话,操作符很有趣,但有趣的地方不在于您是否可以将它与 3 个或 2 个操作符一起使用。相反,当第二个和第三个参数的类型不同时,运算符最有趣的行为是它产生(同样,它被设计为产生一个值)。

与所有其他产生值的表达式一样,三元运算符的类型是固定的,无论条件的值是什么,因此编译器必须确定整个表达式的类型是什么,并且它必须是兼容的类型有两个论点。这些规则非常有趣且过于复杂,无法在此处解释,但您应该看看 Conditional Love: FOREACH Redux

另外,虽然它不适合你的问题,这就是我之前没有带它的原因,不是两个分支需要产生一个值,它们中的一个或两个都可以是throw 表达式,在这种情况下,类型是另一个参数的类型。此外,如果两者都是 throw 表达式或 void 类型的表达式,则三元运算符的类型是 void 本身。

【讨论】:

    【解决方案3】:

    您无法跳过三元运算符的参数。 如果需要调用一次的函数,那么您可以像这样使用。 如果 foo() 和 goo() 的返回值都是布尔值,则可以使用简单的 or 运算符来获取最终结果。

    bool ResultOfFoo =  foo();
    bool ResultOfGoo = goo();
    
    bool RequiredResult = ResultOfFoo ? ResultOfGoo : ResultOfFoo ;
    

    优化

    bool RequiredResult = ResultOfFoo || ResultOfGoo;
    

    【讨论】:

      【解决方案4】:

      目前,(x % 2 == 0)? x *= 2 : x = x 并不比 if 语句好:

      if (x % 2 == 0) x *= 2; // This is okay for one-liners.
      

      如果要保留三元运算符就不用写x = x,可以整行做赋值语句,用三元运算符取值:

      x = (x % 2 == 0)? x * 2 : x;
      

      不过,我还是坚持使用 if 语句。好像更清楚了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-13
        • 1970-01-01
        • 2011-12-27
        • 1970-01-01
        • 2012-12-30
        • 2012-04-02
        相关资源
        最近更新 更多