为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)

Posted

技术标签:

【中文标题】为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)【英文标题】:Why would you use the ternary operator without assigning a value for the "true" condition (x = x ?: 1)为什么要使用三元运算符而不为“真”条件赋值 (x = x ?: 1) 【发布时间】:2011-02-17 20:58:28 【问题描述】:

android 开源 qemu 代码中我遇到了这行代码:

machine->max_cpus = machine->max_cpus ?: 1; /* Default to UP */

这只是一种令人困惑的说法吗:

if (machine->max_cpus) 
   ; //do nothing
 else 
 machine->max_cpus = 1;

如果是这样,不是更清楚吗:

if (machine->max_cpus == 0) machine->max_cpus = 1;

有趣的是,这可以在 gcc 上编译并正常工作,但不能在 http://www.comeaucomputing.com/tryitout/ 上编译。

【问题讨论】:

等等,什么?! ……这对我来说似乎是一个错误。 @Konrad - 可能不是。评论建议该行设置默认值 - 如果“max_cpus 未设置,请设置默认值”。 如果它是合法的,我会避免它,因为它看起来太像它可能是一个错误。在这里我们进行了一次大讨论,证明使用 if 语句会更清楚。 @Konrad:不。在大型特定于编译器的代码库中,您几乎不会用“这是一个扩展”来评论扩展的每一次使用。诚然,与 __builtin_clz 或(在 C++ 中)long long 相比,这对于读者来说是一个特别棘手的问题。 “会不会更清楚”。是的。但如果是machine->max_cpus = arguments->max_cpus ?: 1;,可能就没有那么多了。可能是过度使用在某些情况下可以正常工作但在其他情况下不行的习语的结果。 【参考方案1】:

这是 GNU 中的 permitted,作为 C 的一个不起眼的扩展

5.7 省略操作数的条件

条件中的中间操作数 表达式可以省略。那么如果 第一个操作数非零,其值为 条件的值 表达。

因此,表达式

 x ? : y

如果 x 不为零,则其值为 x; 否则,y的值。

这个例子完全等价 到

 x ? x : y

在这个简单的例子中,能够 省略中间操作数不是 特别有用。当它变成 有用的是当第一个操作数执行时, 或可能(如果是宏参数), 含有副作用。然后重复 中间的操作数将 执行两次副作用。 省略中间操作数使用 没有计算的值 重新计算的不良影响。

您可能已经猜到了,出于可读性和可移植性的原因,建议避免这样做。看到这样一个语法不兼容的 C 扩展,我真的很惊讶。

【讨论】:

有趣。这似乎作为一个句法特征非常有用,而且难以理解。 有趣。那变成了??在 C# 中“如果 x 不为空,则为 x,否则为 y”=> c ??是的 @Potatoswatter:不是真的,因为大概max_cpus 不是布尔值。如果先前的值为 3|| 无法评估为 3 由于这个成语的行为与ternary conditional 不同,在大多数语言中更像logical or,我建议以后将其称为ternarator 我想补充一点,clang 也支持这一点(就像许多 GNU 扩展一样),除非使用 -pedantic 运行。【参考方案2】:

这是一个GCC extension,意思是“如果条件为真,使用它,否则使用这个其他值”,所以

machine->max_cpus = machine->max_cpus ?: 1;

是简写

machine->max_cpus = machine->max_cpus ? machine->max_cpus : 1;

虽然如果条件有副作用,它只会运行一次

【讨论】:

【参考方案3】:

使用 gcc 的 -pedantic 标志,它确实说

foo.c:5:警告:ISO C 禁止 省略 a ? 的中间项: 表达

【讨论】:

【参考方案4】:

这是一个GCC extension,当条件有副作用时它会变得更加有趣和有用。

在这种情况下,是的,我同意它比其他任何东西都晦涩难懂。

【讨论】:

【参考方案5】:

K&R BNF 显示“?”之间需要一个表达式和 ”:”。我认为 gcc 不应该在没有诊断的情况下编译它。

【讨论】:

我相信这是一个非标准的 gcc 扩展。最好避免。【参考方案6】:

还有另一个有用的例子——在调用可能返回 nil 的函数或方法时消除中间变量,我们希望避免调用两次。例如(Objective-C),假设我们想要将一个文件解压成一个数组,如果它存在,则返回一个空数组。

- (NSArray*)hydrateBacklogFromFile:(NSString *path)

    NSArray *backlog = @[];
    NSData *backlogData = [NSData dataWithContentsOfFile:path];
    if (backlogData)
    
        backlog = [NSKeyedUnarchiver unarchiveObjectWithData:backlogData] ?: backlog;
    
    return backlog;

替代方案不太简洁。

- (NSArray*)hydrateBacklogFromFile:(NSString *path)

    NSArray *backlog = @[];
    NSData *backlogData = [NSData dataWithContentsOfFile:path];
    if (backlogData)
    
        NSArray *tempArray = [NSKeyedUnarchiver unarchiveObjectWithData:backlogData];
        if (tempArray != nil)
        
            backlog = tempArray;
        
    
    return backlog;

或者更丑的有多个退货等等。

- (NSArray*)hydrateBacklogFromFile:(NSString *path)

    NSData *backlogData = [NSData dataWithContentsOfFile:path];
    if (backlogData)
    
        NSArray *tempArray = [NSKeyedUnarchiver unarchiveObjectWithData:backlogData];
        if (tempArray != nil)
        
            return tempArray;
        
    
    return @[];

所以它是有用的语法糖,我觉得它相当易读。缺点是

将指针隐式转换为布尔值。这是一个长期的C 约定,但大多数现代语言不允许它,使复杂化 任何移植工作。

正如其他人所说,它也是一个非标准扩展,所以它应该 如果要考虑可移植性,则应避免使用。

【讨论】:

【参考方案7】:

我觉得其他答案没有回答标题中的问题,并且还考虑了标签c。因此,我添加了另一个答案。

我使用这种语法来防止我的代码通过 if 语句变得丑陋。

foo(1) == TRUE ?: error_quit("foo(1) failed");
foo(2) == TRUE ?: error_quit("foo(2) failed");
foo(3) == TRUE ?: error_quit("foo(3) failed");
foo(4) == TRUE ?: error_quit("foo(4) failed");

您可以在行首看到实际的函数调用。与下面的版本相比,前面的if 阻碍了函数调用的直接视图。

if (foo(1) == FALSE) error_quit("foo(1)" failed");
if (foo(2) == FALSE) error_quit("foo(2)" failed");
if (foo(3) == FALSE) error_quit("foo(3)" failed");    
if (foo(4) == FALSE) error_quit("foo(4)" failed");

甚至更难阅读:

if (foo(1) == FALSE)
  error_quit("foo(1)" failed");


if (foo(2) == FALSE)
  error_quit("foo(2)" failed");


if (foo(3) == FALSE)
  error_quit("foo(3)" failed");


if (foo(4) == FALSE)
  error_quit("foo(4)" failed");

【讨论】:

以上是关于为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)的主要内容,如果未能解决你的问题,请参考以下文章

三元条件和赋值运算符优先级

仅当 JavaScript 中的三元运算符中的条件为真时才赋值

三元运算符

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

为啥 Go 没有三元条件运算符 [关闭]

js三元表达式