C中的三元(条件)运算符

Posted

技术标签:

【中文标题】C中的三元(条件)运算符【英文标题】:The ternary (conditional) operator in C 【发布时间】:2010-10-20 00:28:58 【问题描述】:

条件运算符需要什么?在功能上它是多余的,因为它实现了一个 if-else 结构。如果条件运算符比等效的 if-else 赋值更有效,为什么编译器不能更有效地解释 if-else?

【问题讨论】:

关于三元运算符还有很多其他问题,以获取有关其使用的更多信息。 而且它实际上被称为条件运算符。它恰好是唯一的三元运算符,但正如 Jon Skeet 曾经提醒我的那样,以后总会有另一个。 @toast:实际上“三元”是它的常见名称,如果不是更常见的话,那么有条件 @vittore:只是传播一些我通过 Jon Skeet 获得的琐事。试图通过联想和所有这些变得很酷。 ;) 这不是多余的。您可以在许多不能放置 if 块的地方使用它,例如在声明中。 【参考方案1】:

在C语言中,它的真正用处在于它是一个表达式,而不是一个语句;也就是说,您可以将它放在语句的右侧 (RHS)。所以你可以更简洁地写一些东西。

【讨论】:

这就是重点。它将 if/else 转换为表达式,而不是语句。不知何故,我怀疑这里有很多人不明白其中的区别(请不要评论你明白,我不是在和你说话;))。 @查理:+1。我在我的文章中提到了这一点,但最好明确指出这一点。 而且,由于这个特性,它是一个很好的工具,可以让代码更“功能化”而不是“程序化”。【参考方案2】:

给出的其他一些答案很棒。但令我惊讶的是,没有人提到它可以用来以一种紧凑的方式帮助加强const 的正确性。

类似这样的:

const int n = (x != 0) ? 10 : 20;

所以基本上n 是一个const,其初始值取决于条件语句。最简单的替代方法是使n 不是const,这将允许普通的if 对其进行初始化。但是如果你想要const,普通的if是做不到的。你可以做的最好的替代是使用这样的辅助函数:

int f(int x) 
    if(x != 0)  return 10;  else  return 20; 


const int n = f(x);

但三元 if 版本更紧凑,可以说更具可读性。

【讨论】:

嗯,const 确实 出现了,哦,条件运算符晚了 25 年。不过,这是一个可爱的技巧。【参考方案3】:

三元运算符是语法和可读性的便利,而不是性能捷径。对于复杂程度不同的条件,人们对其优点存在分歧,但对于简短的条件,使用单行表达式可能很有用。

此外,由于它是一个表达式,如Charlie Martin wrote,这意味着它可以出现在 C 语言语句的右侧。这对于简洁是有价值的。

【讨论】:

在复杂处理器兴起期间,性能是其优势之一。您不必转储整个处理器管道以获取分支然后可能执行额外的复制,相反它通常只需将单个就绪值推送到管道中。此外,多行表达式通常比 'if (A) return ret1; 更易于阅读。 else if (B) return ret2; ...'。没有什么难读的……返回A? ret0:B? ret1:C? ret2:D? ret3; 三元运算符也降低了代码的圈复杂度。 @AkshayImmanuelD ⇒ 三元运算符不会降低圈复杂度。无论您使用三元运算符还是 if 语句,通过代码的路径数都是相同的。【参考方案4】:

这对于代码混淆至关重要,如下所示:

Look->       See?!

No
:(
Oh, well
);

【讨论】:

注意:要编译上面的代码,只需加上 structint See;*Look;int No,Oh,well;int main() /* 上面的代码放到这里*/ 【参考方案5】:

紧凑性以及将 if-then-else 构造内联到表达式中的能力。

【讨论】:

内联方面是我认为其他方面忽略的一个明显区别。【参考方案6】:

C 中有很多东西在技术上是不需要的,因为它们可以或多或少地用其他东西来实现。这是一个不完整的列表:

    同时 为 功能 结构

想象一下如果没有这些,您的代码会是什么样子,您可能会找到答案。三元运算符是“语法糖”的一种形式,如果使用得当,可以更轻松地编写和理解代码。

【讨论】:

为了继续论证,我们根本不需要 C,因为我们可以用汇编程序做所有必要的事情。 “可移植性适用于不会编写新程序的人。” - 莱纳斯·托瓦兹【参考方案7】:

有时,三元运算符是完成工作的最佳方式。特别是当您希望三元的结果是左值时。

这不是一个很好的例子,但我在更好的东西上画了一个空白。有一件事是一定的,你真正需要使用三元的时候并不经常,虽然我还是用了很多。

const char* appTitle  = amDebugging ? "DEBUG App 1.0" : "App v 1.0";

我要警告的一件事是将三元组串在一起。他们变成了真正的 维护时的问题:

int myVal = aIsTrue ? aVal : bIsTrue ? bVal : cIsTrue ? cVal : dVal;

编辑:这是一个可能更好的例子。您可以使用三元运算符来分配引用和常量值,否则您需要编写一个函数来处理它:

int getMyValue()

  if( myCondition )
    return 42;
  else
    return 314;


const int myValue = getMyValue();

...可能变成:

const int myValue = myCondition ? 42 : 314;

哪个更好是一个值得商榷的问题,我会选择不讨论。

【讨论】:

我同意鞭笞,但我发现这很奇怪。 :) 肯定会在测试示例中使用按字母顺序排列的变量。 是的,当你开始把东西放在括号里时,它会变得非常糟糕。 即使使用一次也可能导致错误。恰当的例子:您的发布版本的标题将是“DEBUG App 1.0”。 如果您想演示将结果用作左值,例如,三元不应该在赋值的左侧吗?【参考方案8】:

由于还没有人提到这一点,因此获得智能printf 语句的唯一方法是使用三元运算符:

printf("%d item%s", count, count > 1 ? "s\n" : "\n");

警告:当您从 C 迁移到 C++ 时,运算符优先级存在一些差异,并且可能会对由此产生的细微错误感到惊讶。

【讨论】:

【参考方案9】:

三元运算符是一个表达式,而不是一个语句,这一事实允许它在宏扩展中用于作为表达式一部分的函数类宏。 const 可能不是原始 C 的一部分,但宏预处理器可以追溯到很久以前。

我看到它使用的一个地方是在一个数组包中,它使用宏来进行边界检查数组访问。检查引用的语法类似于aref(arrayname, type, index),其中arrayname 实际上是一个指向结构的指针,该结构包括数组边界和数据的无符号字符数组,type 是数据的实际类型,index 是索引.这个扩展非常麻烦(我不会从记忆中进行),但它使用了一些三元运算符来进行边界检查。

由于需要返回对象的多态性,因此您不能将其作为 C 中的函数调用来执行。所以需要一个宏来在表达式中进行类型转换。 在 C++ 中,您可以将其作为模板化的重载函数调用(可能用于 operator[]),但 C 没有这样的功能。

编辑:这是我正在谈论的示例,来自 Berkeley CAD 数组包(glu 1.4 版)。 array_fetch 用法的文档是:

type
array_fetch(type, array, position)
typeof type;
array_t *array;
int position;

从数组中获取一个元素。一种 运行时错误发生在试图 参考范围之外的 大批。没有类型检查 给定位置的值 实际上是使用时的类型 取消引用数组。

这里是array_fetch的宏定义(注意使用三元运算符和逗号排序运算符来以正确的顺序执行所有具有正确值的子表达式作为单个表达式的一部分):

#define array_fetch(type, a, i)         \
(array_global_index = (i),              \
  (array_global_index >= (a)->num) ? array_abort((a),1) : 0,\
  *((type *) ((a)->space + array_global_index * (a)->obj_size)))

array_insert 的扩展(如果需要,它会增加数组,就像 C++ 向量一样)甚至更复杂,涉及多个嵌套的三元运算符。

【讨论】:

【参考方案10】:

它是语法糖,是仅包含一条语句的简短 if/else 块的便捷简写。从功能上讲,这两种结构的性能应该相同。

【讨论】:

【参考方案11】:

正如 dwn 所说,在复杂处理器兴起期间,性能是其优势之一,MSDN 博客Non-classical processor behavior: How doing something can be faster than not doing it 举了一个例子,清楚地说明了三元(条件)运算符和 if/else 语句之间的区别。

给出以下代码:

#include <windows.h>
#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>

int array[10000];

int countthem(int boundary)

 int count = 0;
 for (int i = 0; i < 10000; i++) 
  if (array[i] < boundary) count++;
 
 return count;


int __cdecl wmain(int, wchar_t **)

 for (int i = 0; i < 10000; i++) array[i] = rand() % 10;

 for (int boundary = 0; boundary <= 10; boundary++) 
  LARGE_INTEGER liStart, liEnd;
  QueryPerformanceCounter(&liStart);

  int count = 0;
  for (int iterations = 0; iterations < 100; iterations++) 
   count += countthem(boundary);
  

  QueryPerformanceCounter(&liEnd);
  printf("count=%7d, time = %I64d\n",
         count, liEnd.QuadPart - liStart.QuadPart);
 
 return 0;

不同边界的成本有很大不同和奇怪(参见原始材料)。而如果改变:

 if (array[i] < boundary) count++;

 count += (array[i] < boundary) ? 1 : 0;

现在执行时间与边界值无关,因为:

优化器能够从三元表达式中删除分支。

但是在我的台式机 intel i5 cpu/windows 10/vs2015 上,我的测试结果与 msdn 博客完全不同。

使用调试模式时,if/else 开销:

count=      0, time = 6434
count= 100000, time = 7652
count= 200800, time = 10124
count= 300200, time = 12820
count= 403100, time = 15566
count= 497400, time = 16911
count= 602900, time = 15999
count= 700700, time = 12997
count= 797500, time = 11465
count= 902500, time = 7619
count=1000000, time = 6429

和三元运算符成本:

count=      0, time = 7045
count= 100000, time = 10194
count= 200800, time = 12080
count= 300200, time = 15007
count= 403100, time = 18519
count= 497400, time = 20957
count= 602900, time = 17851
count= 700700, time = 14593
count= 797500, time = 12390
count= 902500, time = 9283
count=1000000, time = 7020 

使用释放模式时,if/else 成本:

count=      0, time = 7
count= 100000, time = 9
count= 200800, time = 9
count= 300200, time = 9
count= 403100, time = 9
count= 497400, time = 8
count= 602900, time = 7
count= 700700, time = 7
count= 797500, time = 10
count= 902500, time = 7
count=1000000, time = 7

和三元运算符成本:

count=      0, time = 16
count= 100000, time = 17
count= 200800, time = 18
count= 300200, time = 16
count= 403100, time = 22
count= 497400, time = 16
count= 602900, time = 16
count= 700700, time = 15
count= 797500, time = 15
count= 902500, time = 16
count=1000000, time = 16

三元运算符比我机器上的 if/else 语句慢!

所以根据不同的编译器优化技术,内部运算符和 if/else 的行为可能会有很大的不同。

【讨论】:

【参考方案12】:

C 中一些比较晦涩的运算符之所以存在,仅仅是因为它们允许将各种类似函数的宏实现为返回结果的单个表达式。我想说这是允许?:, 运算符存在的主要目的,即使它们的功能在其他方面是多余的。

假设我们希望实现一个类似函数的宏,它返回两个参数中最大的一个。然后它将被称为例如:

int x = LARGEST(1,2);

将其实现为类似函数的宏的唯一方法是

#define LARGEST(x,y) ((x) > (y) ? (x) : (y))

if ... else 语句不可能,因为它不返回结果值。 注意)

?: 的另一个目的是它在某些情况下实际上增加了可读性。大多数情况下if...else 更具可读性,但并非总是如此。以冗长、重复的 switch 语句为例:

switch(something)

  case A: 
    if(x == A)
    
      array[i] = x;
    
    else
    
      array[i] = y;
    
    break;

  case B: 
    if(x == B)
    
      array[i] = x;
    
    else
    
      array[i] = y;
    
    break;
  ...

这可以替换为更具可读性的内容

switch(something)

  case A: array[i] = (x == A) ? x : y; break;
  case B: array[i] = (x == B) ? x : y; break;
  ...

请注意?:从不产生比if-else更快的代码。这是困惑的初学者创造的一些奇怪的神话。在优化代码的情况下,?: 在绝大多数情况下提供与if-else 相同的性能。

如果有的话,?: 可能if-else,因为它带有强制性的隐式类型提升,即使是不会使用的操作数。但是?: 永远不会比if-else 快。


注意) 现在当然有人会争论并想知道为什么不使用函数。事实上,如果你可以使用一个函数,它总是优于类似函数的宏。但有时你不能使用函数。例如,假设上面示例中的x 在文件范围内声明。初始化器必须是一个常量表达式,所以它不能包含函数调用。其他必须使用类似函数的宏的实际示例包括使用 _Generic 或“X 宏”进行类型安全编程。

【讨论】:

【参考方案13】:

三元 = if-else 的简单形式。它主要用于提高可读性。

【讨论】:

【参考方案14】:

if(0)
do();


if(0)

do();

【讨论】:

以上是关于C中的三元(条件)运算符的主要内容,如果未能解决你的问题,请参考以下文章

Python中的三元运算符是如何实现的

在不使用条件语句和三元运算符的情况下,在 C 中查找最多三个数

mysql三元运算,上下连表

三元表达式是啥?

赋值运算符左侧的三元条件运算符

将三元条件运算符转换为 if 语句?