三元运算符与 if 语句的优点

Posted

技术标签:

【中文标题】三元运算符与 if 语句的优点【英文标题】:Benefits of ternary operator vs. if statement 【发布时间】:2011-05-10 16:25:55 【问题描述】:

我正在浏览一些代码,并在其中发现了一些三元运算符。这段代码是我们使用的库,应该很快。

我在想我们是否要节省任何东西,除了那里的空间。

你的经验是什么?

【问题讨论】:

如果三元比 if 语句更快(反之亦然),编译器肯定会将一个转换为另一个。所以它们不应该有不同的性能特征(假设你的陈述质量相同)。 如果有的话,它是一个 µ 优化。如有疑问:基准测试。 Benefits of using the conditional ?: (ternary) operator 的可能重复项 呵呵...为了推销自己的问题而碰到老问题?你可能会发现自己禁止了我的朋友... nawfal:该链接专门用于 C#,所以恕我直言,这不是一个好的复制品 【参考方案1】:

性能

三元运算符的性能不应与编写良好的等效 if/else 语句在性能上有所不同...它们很可能会解析为抽象语法树中的相同表示,进行相同的优化等。

你只能做的事情? :

如果您正在初始化一个常量或引用,或者确定要在成员初始化列表中使用哪个值,则不能使用 if/else 语句,但 ? : 可以是:

const int x = f() ? 10 : 2;

X::X() : n_(n > 0 ? 2 * n : 0)  

简洁代码的分解

使用? : 的关键原因包括本地化,并避免重复重复相同语句/函数调用的其他部分,例如:

if (condition)
    return x;
else
    return y;

...只比...更可取

return condition ? x : y;

...如果与非常缺乏经验的程序员打交道,或者某些术语过于复杂以至于? : 结构在噪音中迷失了,则出于可读性的考虑。在更复杂的情况下,例如:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);

等价的if/else:

if (condition1)
    if (condition2)
        if (condition3)
            fn(t1, t2, t3);
        else
            fn(t1, t2, f3);
    else if (condition3)
            fn(t1, f2, t3);
        else
            fn(t1, f2, f3);
else
    if (condition2)
       ...etc...

编译器可能会或可能不会优化掉很多额外的函数调用。

此外,? 允许您选择一个对象,然后使用其中的一个成员:

(f() ? a : b).fn(g() ? c : d).field_name);

等效的if/else 将是:

if (f())
    if (g())
        x.fn(c.field_name);
    else
        x.fn(d.field_name);
else
    if (g())
        y.fn(c.field_name);
    else
        y.fn(d.field_name);

命名临时变量不能改进上面的 if/else 怪物吗?

如果表达式t1f1t2 等过于冗长而无法重复输入,创建命名临时变量可能会有所帮助,但是:

1234563这更复杂,更容易出错。

c ? x : y 评估 c 然后评估 xy,这使得在使用它之前可以安全地说测试指针不是nullptr,同时提供一些后备值/行为。该代码仅获得实际选择 xy 中的任何一个的副作用。对于命名的临时对象,您可能需要在它们的初始化范围内使用 if / else? :,以防止不需要的代码执行,或者代码执行的频率超出预期。

功能区别:统一结果类型

考虑:

void is(int)  std::cout << "int\n"; 
void is(double)  std::cout << "double\n"; 

void f(bool expr)

    is(expr ? 1 : 2.0);

    if (expr)
        is(1);
    else
        is(2.0);

在上面的条件运算符版本中,1 经历了到double 的标准转换,以便匹配2.0 的类型,这意味着即使在true/1 情况下也会调用is(double) 重载。 if/else 语句不会触发此转换:true/1 分支调用 is(int)

您也不能在条件运算符中使用整体类型为 void 的表达式,而它们在 if/else 下的语句中有效。

强调:需要价值的行动之前/之后的价值选择

有不同的重点:

if/else 语句首先强调分支,而要做的事情是次要的,而三元运算符强调要做什么而不是选择要使用的值。

在不同的情况下,两者都可以更好地反映程序员对代码的“自然”视角,使其更易于理解、验证和维护。您可能会发现自己根据在编写代码时考虑这些因素的顺序来选择一个而不是另一个 - 如果您已经开始“做某事”,那么您会发现您可能会使用几个(或几个)值中的一个来做? : 是表达这一点并继续您的编码“流程”的最少破坏性方式。

【讨论】:

没有三元运算符的该函数调用的直接等效项必须使用中间变量和单独的条件。只要它们的作用域是函数的本地范围,编译器就应该能够将它们优化掉。如果没有它们,您将不得不编写一个无法阅读和调试的页面,尽管现代编译器可能会在优化它方面做得很好。 @thkala:很好地阐述了我过于简洁的“创建临时对象也很麻烦,但保证与三元运算符类似的性能。”。编译器可能会删除至少一些 if/else 函数树,但由于寄存器分配、深度限制等因素,事情可能会变得复杂,而且很难知道它是否能顺利完成。 请见谅。虽然你的解释是对的。条件运算符可用于 if()...else() 可能不在那里使用的表达式。我使用这个技巧以一种不同的方式解决了 Scott Meyer 的《更有效的 C++》中的第 10 项,其中我避免使用 auto_ptr 。看看:siddhusingh.blogspot.com @TonyD :我认为您被代码误导了。尝试在编辑器中重新格式化代码。没有任何内存泄漏。我所做的是:我正在根据另一个变量的值初始化成员变量指针,如果不是,我将其分配为 NULL,如果为真,那么我首先将其分配为 NULL,然后使用逗号运算符,我正在调用 new 运算符。如果您使用逗号运算符,则将最后一个值分配给该值。如果出现任何异常,我会使用 catch(...) 捕获它,清理所有分配的内存,然后从构造函数本身重新抛出它。 @siddhusingh:抱歉 - 现在注意到您的初始化程序列表正在嵌入这些语句,因此任何指针都会传递回数据成员。但是,如果new Image() 抛出,那么theAudioClip 将是deleted,尽管未初始化。你可以解决这个问题:所有原始指针都应该在第一个数据成员(即theName)构造函数可能抛出之前初始化,假设你在构造函数周围有一个try/catch,如: theName((theImage = theAudioClip = nullptr, name))。首先使用指针数据成员更好,但仍然很脆弱。【参考方案2】:

在我看来,三元运算符相对于普通 if 语句的唯一潜在好处是它们能够用于初始化,这对 const 尤其有用:

例如

const int foo = (a > b ? b : a - 10);

如果不使用函数 cal 也无法使用 if/else 块执行此操作。如果您碰巧有很多这样的 const 案例,您可能会发现通过 if/else 正确初始化 const 有一点好处。测量它!可能甚至无法衡量。我倾向于这样做的原因是,通过将其标记为 const,编译器知道我稍后执行的操作可能/会意外更改我认为已修复的内容。

实际上我要说的是三元运算符对于 const 正确性很重要,而 const 正确性是一个很好的习惯:

    让编译器帮助您发现自己犯的错误,从而节省大量时间 这可能会让编译器应用其他优化

【讨论】:

【参考方案3】:

嗯……

我用 GCC 和这个函数调用做了一些测试:

add(argc, (argc > 1)?(argv[1][0] > 5)?50:10:1, (argc > 2)?(argv[2][0] > 5)?50:10:1, (argc > 3)?(argv[3][0] > 5)?50:10:1);

使用 gcc -O3 生成的汇编代码有 35 条指令。

if/else + 中间变量的等效代码有 36。使用嵌套 if/else 使用 3 > 2 > 1 的事实,我得到 44。我什至没有尝试将其扩展为单独的函数调用。

现在我没有进行任何性能分析,也没有对生成的汇编代码进行质量检查,而是在像这样没有循环等简单的事情上。我相信越短越好。

看来三元运算符毕竟还是有一些价值的 :-)

当然,这只是在代码速度绝对至关重要的情况下。嵌套时,if/else 语句比 (c1)?(c2)?(c3)?(c4)?:1:2:3:4 更容易阅读。并且将巨大的表达式作为函数参数是有趣的。

另外请记住,嵌套的三元表达式使得重构代码——或者通过在某个条件下放置一堆方便的 printfs() 来进行调试——变得更加困难。

【讨论】:

顺便说一句,有趣的是,当我试图在 3 > 2 > 1 代码中比编译器更聪明时,结果适得其反,正如我所料。结论:永远不要试图超越编译器!【参考方案4】:

如果您从性能的角度担心它,那么如果两者之间有任何不同,我会感到非常惊讶。

从外观和感觉的角度来看,这主要取决于个人喜好。如果条件很短并且真/假部分很短,那么三元运算符就可以了,但是在 if/else 语句中,任何更长的东西都会更好(在我看来)。

【讨论】:

【参考方案5】:

您假设 必须 两者之间有区别,而事实上,有许多语言放弃了“if-else”语句而支持“if-else”表达式(在这种情况下,它们甚至可能没有三元运算符,不再需要)

想象一下:

x = if (t) a else b

无论如何,三元运算符是某些语言(C、C#、C++、Java 等)中的表达式,它们 具有“if-else”表达式,因此它提供有不同的角色

【讨论】:

以上是关于三元运算符与 if 语句的优点的主要内容,如果未能解决你的问题,请参考以下文章

php 中 三元运算和 IF语句 运行速度

PHP三元运算符

JavaScript 三元运算符变成完整的 if/else 语句问题

如何在三元运算符中编写多个语句?

三元?运算符与 C# 中的传统 If-else 运算符 [重复]

如何使用 JavaScript 中的三元运算符更改函数中的 if else 语句?