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

64

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


关于三元运算符的使用情况,还有很多其他问题需要了解。 - Dana the Sane
8
它实际上被称为条件运算符。它恰好是唯一的三元运算符,但正如Jon Skeet曾提醒我一样,以后总有可能会出现另一个。 - Benjamin Autin
2
@toast:实际上,“三元运算符”是它的常用名称,如果不是比条件语句更常见的话。 - vittore
3
@vittore:我只是在分享一些我通过Jon Skeet学到的小知识。试图通过联想变得更酷。 ;) - Benjamin Autin
4
不是多余的。您可以在许多无法放置if块的地方使用它,例如在声明中。 - Leo
C++三目运算符 vs if-else - Ciro Santilli OurBigBook.com
14个回答

160
在C语言中,它的真正用途是它是一个表达式而不是语句;也就是说,您可以将其放在语句的右侧(RHS)。因此,您可以更简洁地编写某些内容。

19
这是关键点。它将if/else转换为表达式,而不是语句。我怀疑在这里有相当多的人不明白这种区别(请不要评论说你知道,我不是在和你说话 ;)). - Darren Clark
3
@Charlie: +1。我在我的评论中提到了这一点,但将其明确表述很好。 - John Feminella
1
由于这个特性,它是一个很好的工具,可以使代码更加“功能化”,而不是“过程化”。 - Charles Bretana

90

其他回答很好。但我很惊讶没有人提到它可以用来以简洁的方式帮助实施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);

但三元条件运算符版本更加简洁,可以说更易读。


5
嗯,const关键字确实在条件运算符出现大约25年后才问世。不过这个语法技巧挺巧妙的。 - Charlie Martin

69

三目运算符是一种语法上和可读性上的便利,而不是性能上的捷径。对于复杂度不同的条件语句,人们对它的价值观存在分歧,但对于短条件语句来说,拥有一个单行表达式很有用。

此外,由于它是一个表达式,正如Charlie Martin所写的那样,在C语言中它可以出现在语句的右侧。这对于简洁很有价值。


5
性能是复杂处理器崛起期间的一个优点之一。您不必倒空整个处理器管道以执行分支并可能执行额外的复制,而通常只需将单个准备好的值推入管道即可。 此外,与类似于“if (A) return ret1; else if (B) return ret2; ...”之类的表达式相比,它通常更易读且适用于多行。在以下代码中没有任何难以理解的地方: return A? ret0 : B? ret1 : C? ret2 : D? ret3; - dwn
1
三元运算符还可以减少代码的圈复杂度。 - AlphaGoku
1
@AkshayImmanuelD ⇒ 三元运算符并不能降低圈复杂度。无论是使用三元运算符还是 if 语句,代码的路径数量都是相同的。 - John Feminella

43

这对于代码混淆至关重要,比如:

Look->       See?!

No
:(
Oh, well
);

7
注意:要使上面的代码编译通过,只需添加struct{int See;}Look;int No,Oh,well;int main(){ / 上面的代码放在这里 */ }。 - Artelius

13

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


1
内联方面是我认为其他人忽略了的一个明显差异。 - Man Vs Code

11

在C语言中,有很多东西并不是必需的,因为它们可以在其他方面更或多或少地实现。以下是一个不完整的列表:

  1. while 循环
  2. for 循环
  3. 函数
  4. 结构体

想象一下如果没有这些内容你的代码会是什么样子,你也许就能找到答案了。三目运算符是一种"语法糖",如果谨慎使用和技巧得当,会使编写和理解代码变得更容易。


4
继续这个论点,我们实际上根本不需要 C,因为我们可以用汇编语言完成所有必要的工作。 - Ether
1
“可移植性是给那些不会写新程序的人准备的。”- Linus Torvalds - Chris Lutz

9

有时候,三元运算符是完成工作的最佳方式。特别是当你想要三元运算符的结果是一个l-value时。

这不是一个好的例子,但我在寻找更好的例子上一片空白。确实,并不经常真正需要使用三元运算符,尽管我仍然经常使用它。

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

然而,我要警告的一件事是不要把三元运算符串在一起。它们在维护时会变成一个真正的问题:

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

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

int getMyValue()
{
  if( myCondition )
    return 42;
  else
    return 314;
}

const int myValue = getMyValue();

...可以变成:

const int myValue = myCondition ? 42 : 314;

哪个更好是一个有争议的问题,我选择不进行辩论。


我同意关于抨击的观点,但我发现那个东西很容易读懂。:) 在测试示例中,变量按字母顺序对齐。 - Darren Clark
当你开始把东西放在括号里时,情况会变得非常棘手。 - John Dibling
即使只是一次使用也可能导致 bug。例如:你的发布版本将会有标题“DEBUG App 1.0”。 - Michael Myers
如果您想展示结果作为左值的使用,那么三元运算符不应该在赋值语句的左侧吗?例如: - Razzle

8
三元操作符是一个表达式而不是语句,这使得它可以在宏扩展中用作表达式的一部分,用于函数样式的宏。虽然Const可能不是C语言的原始部分,但是宏预处理器早已存在。
我看到过它被用在一个数组包中,该包使用宏进行边界检查的数组访问。检查引用的语法类似于aref(arrayname,type,index),其中arrayname实际上是一个指向结构体的指针,该结构体包括数组边界和用于数据的无符号字符数组,type是数据的实际类型,index是索引。这个宏的扩展非常复杂(我不会从记忆中做),但是它使用了一些三元操作符来进行边界检查。
在C中,由于需要返回对象的多态性,不能将其作为函数调用执行。因此,需要使用宏在表达式中进行类型转换。 在C ++中,您可以通过模板化的重载函数调用(可能是operator [])来执行此操作,但是C没有这样的功能。
编辑:这是我所说的示例,来自伯克利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)))

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

8

既然还没有人提到这点,那么得到智能的printf语句的唯一方法就是使用三元运算符:

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

注意:当从C转移到C ++时,操作符优先级存在一些差异,可能会因此产生微妙的错误。


4

这是一种语法糖,也是一个方便的简写形式,用于只包含一个语句的简短 if/else 代码块。在功能上,两个结构应该执行相同。


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