为什么C语言没有逻辑赋值运算符?

48
我需要编写一个类似于以下形式的语句的代码
a = a || expr;

如果 a 没有被设置,expr 会被评估并将结果分配给 a。 这依赖于逻辑 OR 的短路功能。

当然,上述内容的更短的编写方式是:

a ||= expr;

但是(令我惊讶的是),C语言没有逻辑赋值运算符。

所以我的问题有两个方面。首先,在标准C中是否有更短的方式来编写第一个语句(三元运算符甚至更差 - a = a?a:expr 要求我将a拼写三次)。

其次,为什么C语言没有逻辑赋值运算符?我能想到的可能原因是:

  • 它使语法更难解析?
  • 在处理这些情况时存在一些短路的细微差别?
  • 认为它是多余的(但这不是反对所有运算符分配的论点吗?)

编辑

请解锁此问题,因为:

  • 它被链接到的问题(作为所谓的重复问题)未得到回答。那个问题的(已接受的)答案表明 ||=不存在,因为它会重复 |=的功能。那是错误的答案。|=不进行短路计算。

  • C和C ++不是相同的语言。我想知道为什么C语言没有它。事实上,像C ++和特别是Java这样的派生语言(它没有遭受像Edmund's答案中所暗示的旧代码问题)使问题更加有趣。

编辑2

现在似乎我的最初意图是错误的。在语句a = a || expr中(其中a为整数,expr返回一个整数值),首先会将aexpr隐式转换为“布尔”值,然后将“布尔”值分配给a。这是不正确的-整数值将丢失。感谢Jens和Edmund。

因此,关于第一个问题,正确的方式不是替代方式 :) ,可以编写以下代码来实现我的意图:

if (!a) a = expr;
或者
a = a ? a : expr;

我认为它们应该被同样地优化,尽管个人而言我更喜欢第一个(因为它少输入了一个a)。

然而,问题的第二部分仍然存在。Jens和Edmund所提出关于a ||= expr中歧义性的论点同样适用于a = a || expr。赋值情况可以简单地视为正常情况:

  • a转换为布尔值
  • 如果它是true,整个表达式的值等于a的布尔值
  • 否则,求值expr,将结果转换为布尔值,赋值给a并返回它

上述步骤似乎对赋值和正常情况都一样。


1
这个问题不是上面提到的“可能重复”的精确副本。那个问题问为什么没有||=等语法简写。而这个问题问的是为什么C语言不允许通过布尔运算符传递非布尔值的技巧。 - Edmund
1
我本来要发表一个答案:如果这些可以使用短路,那就太好了。有一个选项 if ( ! a ) a = expr; ,它非常清晰简洁。至于技巧,需要注意的是布尔值的加法和乘法分别产生 OR 和 AND 函数,因此只要不需要短路或强制转换为 bool+=*= 的效果就像 ||=&&= - Potatoswatter
1
由于此问题已经过早关闭,我在评论中回答。我认为这种情况不存在,因为它的解释会产生歧义。在 a |= expr 中应该分配什么值,expr 的结果还是它的逻辑值?作为一个整体,a |= expr 的类型应该是什么,inta 的类型还是提升 aexpr 的结果?我认为这些问题没有直接的解决方案,可能因此任何可能考虑过这个问题的人都很快就放弃了。我个人会选择 a = (a ? a : expr) 并让编译器优化赋值。 - Jens Gustedt
1
有一个 GCC 扩展 a ?: b,它的意思是 a ? a : b,但不会进行额外的求值。 - M.M
添加 &&=||= 运算符对编译器的复杂度没有任何显著影响,就像 += 一样容易实现。更重要的是这些运算符的实用性非常有限。你提出了一个问题:||&& 的结果始终为 0 或 1,因此赋值运算符将有条件地使用 0 或 1 覆盖 LHS,具体取决于 LHS 中的值是否类似 true(非零)或 false(零),以及 RHS 是否评估为 true 或 false。我不记得曾经感到缺少这样的功能。 - Jonathan Leffler
显示剩余4条评论
5个回答

10

a ||= expr 的问题在于其等价的 a = a || expr 短路求值机制存在。

如果希望 a ||= expra = a || expr 一样工作,可以考虑 OP 的观点:

"在语句 a = a || expr 中,首先会将 a 和 expr 都隐式转换为“布尔”类型,"

这并不完全正确。如果 a 的值为 true,则不会对 expr 进行转换。这可能会导致问题,例如 expr 是像 scanf()rand() 这样会影响程序状态的函数。

类似 a ||= scanf("%d", &i) != 1; 这样的代码只会尝试扫描具有 a 中的 false 值的数据。虽然可以通过扩展语言来实现这种方式,但向当前的 ||&& 集合添加其他短路运算符可能会导致更多的编码问题而不是清晰的简化。

另一方面,这是一种快速但难以理解的编写函数在错误时返回非零代码的代码方式。

// Perform functions until an error occurs.
bool error = foo1();
error &&= foo2();  // Only valid if C was extended with &&=
error &&= foo3();

5
由于运算符||和&&的返回类型与其左参数的类型不同,因此:
无论左参数是任何整型、浮点型或指针类型,||和&&的返回类型始终是int1。操作数也不必是相同类型的。因此,将x ||= y定义为x = x || y,将x &&= y定义为x = x && y,与其他增强赋值一致,大多数类型都不能在参数中存储结果。
你可以想出其他定义,例如,将x ||= y定义为if(!x) x = y,将x &&= y定义为if(!y) x = y,但这不是很明显,也不是那么有用,因此未被包括。 1在C++中,它是bool。

3
我想简单的答案是 || 是一个布尔运算符:在C语言中,"布尔"表示0或1。操作数会隐式转换为布尔值(我没有检查规范是否实际如此,但这就是C语言的行为),结果也是一个布尔值。
修改语义以支持这种模式可能是可行的 - 直到有人依赖于||一直做着它该做的事情。

3
在C语言中没有boolean类型,因此操作数不会被转换为boolean||&&的结果也不是boolean - qrdl
3
@qrdl,你的看法是正确的,虽然C99引入了"_Bool",但"&&"、"||"(和"!")的结果被定义为0或1。请查看C99标准§6.5.13和§6.5.14。 - schot
1
谢谢。我的使用的“引号”表示那是大致的意思。如果我指的是神话中的“布尔”类型,我会明确指出。我的整个回答都是以行为为基础,而不是抽象规范。 - Edmund
细节:结果是布尔值。--> 结果是类型为int的布尔值,取值为0或1。 - chux - Reinstate Monica

3

我找不到任何特别的原因,解释为什么运算符在C99中不存在。

所以我唯一能找到的原因是,在C89中没有布尔类型,那些布尔运算符只打算用于if语句中。

例子:

int i = 5;

/* This should not make any difference,
   since or'ing with false, shouldn't change
   the value... dib di dib diddy...*/
i ||= 0; /* Actually: i = i || 0, which gives 'true' */

i 现在是 '1',对于大多数人来说这是相当反直觉的。

显然,如果没有布尔类型,这个运算符不会带来任何清晰度或编码改进,与另一个运算符进行或运算将有意义。

在我看来,实现 a ||= b; 作为 if(!a) a = b; 将非常简单,并且已经被 Lua 等语言实现了。

所以你的问题似乎有点,为什么 C 被设计成它被设计成的样子。 如果这个问题是关于 C++ 的,你可以问 Bjarne Stroustrup 并询问他,他到底在想什么。由于这不是这种情况,这对我来说似乎是一个死胡同,因为标准已经写了很长时间了,你不能真正再问人们为什么了。

另一方面,这个不完整的运算符集应该(在我看来)使用类似于你的符号来补充,因为在我看来,没有理由反对它。

我希望我能有所帮助。


0
一个简单的解释是这样的。
bool resultsComputeAll = false;
bool resultsUntilFirst = false;
for (int i = 0; i < 10; ++i) {
    resultsComputeAll = compute(i) || resultsComputeAll;
    resultsUntilFirst = resultsUntilFirst || compute(i);
}

result ||= compute(i)这个表达式的意思是什么?由于含义不明确,最好不要定义。


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