2

三元运算符是否可以有一个空白的第二个或第三个参数,或者有一个非上下文特定且意味着“什么都不做”的参数?在下面的示例中,我想要一个三元运算符将一个整数变量乘以 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返回 a bool。假设由于某种原因不能再次调用这两个函数,例如后续调用中的状态更改。

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

4 回答 4

7

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

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

就个人而言,我想我只会使用一点点数学:

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

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

至于为什么后者会(或至少可能)更可取,主要归结为您是否真的关心这种情况下的性能的问题。如果您处于性能无关紧要的情况,那么类似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,这应该在几乎任何英特尔处理器上执行不超过 4 个时钟周期,回到 386 左右。更好的是,我希望任何处理器都能产生任何编译器非常相似的结果——对于几乎任何合理的处理器,从源代码到汇编语言的直接、字面翻译将在四条指令中进行实际的数学运算,每条指令都尽可能简单和快速。

使用该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,否则我什至不确定是否能获得同样好的结果——而且它们变得更好的机会充其量让我觉得非常渺茫。

于 2013-09-07T02:30:12.730 回答
2
(x % 2 == 0) ? x *= 2 : x = x;

条件运算符不是if 语句,它旨在产生一个 value,如果您不需要该值,就像上面的情况一样,只需按原样键入if

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

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

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

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

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

于 2013-09-07T02:29:22.653 回答
1

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

bool ResultOfFoo =  foo();
bool ResultOfGoo = goo();

bool RequiredResult = ResultOfFoo ? ResultOfGoo : ResultOfFoo ;

优化

bool RequiredResult = ResultOfFoo || ResultOfGoo;
于 2013-09-07T02:10:25.530 回答
0

目前,(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 语句。似乎更清楚了。

于 2013-09-07T01:59:13.977 回答