条件运算符需要什么?在功能上它是多余的,因为它实现了一个 if-else 结构。如果条件运算符比等效的 if-else 赋值更有效,为什么编译器不能更有效地解释 if-else?
14 回答
在 C 中,它的真正用处在于它是一个表达式而不是一个语句。也就是说,您可以将它放在语句的右侧 (RHS)。所以你可以更简洁地写一些东西。
给出的其他一些答案很棒。但令我惊讶的是,没有人提到它可以用来以const
一种紧凑的方式帮助强制执行正确性。
像这样的东西:
const int n = (x != 0) ? 10 : 20;
所以基本上n
是 a const
,其初始值取决于条件语句。最简单的替代方法是制作n
not a const
,这将允许普通人if
对其进行初始化。但如果你想这样const
,普通的就做不到if
。您可以做的最好的替代品是使用这样的辅助函数:
int f(int x) {
if(x != 0) { return 10; } else { return 20; }
}
const int n = f(x);
但是三元 if 版本要紧凑得多,并且可以说更具可读性。
三元运算符是语法和可读性的便利,而不是性能捷径。对于不同复杂度的条件,人们对它的优点存在分歧,但对于简短的条件,使用单行表达式可能很有用。
此外,由于它是一个表达式,正如Charlie Martin 所写,这意味着它可以出现在 C 语言语句的右侧。这对于简洁是有价值的。
这对于代码混淆至关重要,如下所示:
Look-> See?!
No
:(
Oh, well
);
紧凑性和将 if-then-else 构造内联到表达式中的能力。
C 中有很多东西在技术上是不需要的,因为它们可以或多或少地在其他东西方面很容易实现。这是一个不完整的列表:
- 尽管
- 为了
- 职能
- 结构
想象一下,如果没有这些,您的代码会是什么样子,您可能会找到答案。三元运算符是“语法糖”的一种形式,如果使用得当,可以更轻松地编写和理解代码。
有时,三元运算符是完成工作的最佳方式。特别是当您希望三元的结果是左值时。
这不是一个很好的例子,但我在更好的东西上画了一个空白。有一件事是确定的,当你真正需要使用三元时并不经常,尽管我仍然使用它很多。
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;
哪个更好是一个有争议的问题,我将选择不辩论。
由于还没有人提到这一点,获得智能printf
语句的唯一方法是使用三元运算符:
printf("%d item%s", count, count > 1 ? "s\n" : "\n");
警告:当您从 C 迁移到 C++ 时,运算符优先级存在一些差异,并且可能会对由此产生的细微错误感到惊讶。
三元运算符是一个表达式,而不是一个语句,这一事实允许它在宏扩展中用于用作表达式的一部分的类似函数的宏。const 可能不是原始 C 的一部分,但宏预处理器可以追溯到很久以前。
我见过它使用的一个地方是在一个数组包中,它使用宏来进行边界检查数组访问。检查引用的语法类似于aref(arrayname, type, index)
,其中 arrayname 实际上是指向结构的指针,该结构包括数组边界和数据的无符号字符数组,类型是数据的实际类型,索引是索引。这个扩展非常麻烦(我不会从记忆中进行),但它使用了一些三元运算符来进行边界检查。
由于需要返回对象的多态性,因此您不能将其作为 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)))
array_insert 的扩展(如果需要,它会增长数组,如 C++ 向量)甚至更复杂,涉及多个嵌套的三元运算符。
它是语法糖,是仅包含一个语句的简短 if/else 块的便捷简写。从功能上讲,两种结构的性能应该相同。
就像 dwn 说的,在复杂处理器兴起期间,性能是它的好处之一,MSDN 博客非经典处理器行为:如何做某事可以比不做更快给出了一个例子,它清楚地说明了三元(条件)运算符和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 的行为可能会有很大不同。
C 中一些比较晦涩的运算符之所以存在,仅仅是因为它们允许将各种类似函数的宏实现为返回结果的单个表达式。我想说这是允许
?:
and,
运算符存在的主要目的,即使它们的功能在其他方面是多余的。假设我们希望实现一个类似函数的宏,它返回两个参数中的最大值。然后它将被称为例如:
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 宏”进行类型安全编程。
三元 = if-else 的简单形式。它主要用于可读性。
一样
if(0)
do();
if(0)
{
do();
}