一门语言是否需要前置递增 (++x) 和后置递增 (x++)?

3

我从未见过实际代码中使用前缀递增后缀递增的用例。我最常见到它们的地方是谜题。

我的意见是,它会引入更多的混乱,而不是有用。

  • 是否有任何真正的用例场景需要使用这个操作符?
  • 使用+=操作符不能达到同样的效果吗?

    y = x++

    y = x
    x += 1


12
必要吗?我喝自己的尿并不是必要的,但它是无菌的,而且我喜欢它的味道。- Patches O'Houlihan - kenj0418
1
作为一名计算机科学工程专业的学生,你是否对 x++ 和 ++x 的区别感到困惑?而且你从未觉得用不同的方式表达类似的事情有什么用处? - Mark Bolusmjak
3
针对提问者,虽然我认为他的问题是“错误”的,但我更加坚信这是一个真正的问题。 - paxdiablo
3个回答

7

这只是一种更简洁的写法,对于那些不深入了解C语言的人才会感到困惑 (a)。同样的理由也可以用于替换以下代码:

for (i = 0; i < 10; i++)
    printf ("%d\n", i);

使用:

i = 0;
while (i < 10) {
    printf ("%d\n", i);
    i = i + 1;
}

因为任何一个for循环都可以用while循环来实现,或者:

i = 0;
loop: if (i < 10) {
    printf ("%d\n", i);
    i = i + 1;
    goto loop;
}

因为任何循环结构都可以由条件和 goto 构建而成。但是(我希望)你不会这样做,对吧?


(a) 我有时喜欢用简单的语句和副作用来解释这个问题,这样可以使 C 代码更加简洁,通常不会或几乎不会影响可读性。

对于该语句:

y = x++;

语句是将x赋值给y,并具有副作用,即之后增加了x++x也是一样的,只是副作用发生在前面。

同样,赋值的副作用是它会被评估为已分配的值,这意味着您可以执行以下操作:

while ((c = getchar()) != -1) count++;

并且使得像这样的事情变得简单:
42;

这是一些完全有效但无用的C语句。


1
为什么不使用 for (i = 0; i < 10; i+=1) - Anantha Kumaran
1
void strcpy(char* a, char* b) { while (*a++ = *b++) ; } - Travis Gockel
4
@Ananantha,你没有抓住重点。我的意思是,在这种特定情况下,你应该使用“for”而不是“while”(i++/i+=1的区别在这种情况下并不重要)。最重要的是:利用你掌握的语言特性,认真理解它们。 - paxdiablo
啊,当你写下最后一条语句时,Java(或者至少是我的IDE)会开始抱怨:它会报告一个错误,提示“不是语句”。 - MC Emperor
@MC,对于这种情况,Java比C语言要严格一些。 - paxdiablo

6

如果您考虑到历史和它们的发明时间,前缀和后缀递增运算符会更有意义。

在C语言基本上是PDP-11机器的高级汇编语言</ flamebait>时代,远在我们现在拥有的优化编译器之前,有一些常见的习惯用法是后缀递增运算符非常适合的。比如:

char* strcpy(char* src, char* dest)
{
  /* highly simplified version and likely not compileable as-is */
  while (*dest++ = *src++);
  return dest;
}

这段代码生成了PDP-11(或其他)机器语言代码,大量使用了底层寻址模式(如相对直接和相对间接),其中包含了这些前后增减操作。

那么回答你的问题:现在的编程语言是否“需要”这些呢?当然不需要。可以证明,在计算方面,你只需要非常少的指令。更有趣的问题是:“这些功能是否可取?”对此,我的回答是“是的,但有限制条件”。

以你的例子为例:

y = x;
x += 1;

对比。

y = x++;

我能想到两个优点。
  1. 代码更加简洁。只要我了解语言,就可以在一个位置上找到理解您正在做什么的所有内容,而不是分散开来。虽然在两行之间“分散开来”似乎是一件琐碎的事情,但如果你要执行数千次,最终会产生很大的差异。
  2. 即使由劣质编译器生成的代码,在第二种情况下也很可能是原子性的。而在第一种情况下,除非您有一个好的编译器,否则它很可能不是原子性的。(并非所有平台都有良好、强大的优化编译器。)
此外,我发现你说的是“+=”,这本身就是一个“无必要”的说法,“x = x + 1;”就足够了。毕竟,我想不出任何使用案例场景,不能通过“_=_+_;”很好地完成。

2
使用类似 x[f(y)][g(z)] += 10 的代码实际上比 x[f(y)][g(z)] = x[f(y)][g(z)] + 10 更高效,因为编译器在第一种情况下只需要评估一次 x[f(y)][g(z)],但在第二种情况下可能需要评估两次。 - Gabe
@gabe:现代编译器大多数时候都能自动处理好这些问题。(事实上,用手写汇编语言很难甚至不可能超越现代优化编译器的性能。)@paxdiablo:C语言源于PDP-11,而不是PDP-8汇编语言。@Travis G: "多次计算" 是最早的 peephole 优化之一。即使在旧的编译器中,x++、x+=1 和 x=x+1 的代码也会生成相同的代码(当然,更复杂的 lvalues 需要更好的优化器)。 - JUST MY correct OPINION
只有在C++中声明f和g为“const”或者你有一个可以检测到f和g没有副作用的整个程序优化器的情况下,才能安全地将x[f(y)][g(z)]优化为只评估一次。这种优化是可能的,但远非保证!如果f和g是外部函数,编译器别无选择,只能评估两次。 - Gabe
区分 a += b;a = a + b; 的一个优点是,如果两者都可以重载(如在 C++ 中),前者可以表示“追加”,而后者则设置了一个全新的值。不过,这两种语句的最终结果在实践中几乎相同,差别主要在于效率。因此,合理的代码应该使这两种语句的最终结果基本相同。 - cHao
1
@cHao,我理解你关于“合理”的代码的说法,但是要明确的是,C++改变这样的惯用语(通过为a+=ba=a+b提供不同的能力)并不是一个“加分项”,无论哪个类实现它。这违背了语言的基本原则(由于内置算术类型已经很好地理解了),并且会对阅读代码的模式识别造成极大的破坏。C++的粉丝们毫无疑问会对此进行负面评价,但在我看来,这是因为混淆了“能做”和“应该做”的编程概念,在C++中这种情况太常见了。 - user4229245
显示剩余8条评论

0

你在这里无意中提出了一个更大的问题,随着岁月(几十年)的流逝,这个问题会变得越来越明显。

语言经常犯错误,当它们不应该提供“能力”时。在我看来,++应该是一个独立的语句,而绝对不是一个表达式运算符。

请尽量牢记以下内容:目标不是为有能力的工程师创建可读的代码。目标是为那些在凌晨3点精疲力尽、兴奋不已的有能力的工程师创建可读的代码。

如果一个工程师对你说:“所有的代码结构都可能会让你陷入麻烦。你只需要知道你在做什么。”,那么笑着走开吧,因为他刚刚暴露了自己是问题的一部分。

换句话说,请永远不要编写像这样的任何代码:

a[aIndex++] = b[++bIndex];

你可以在这里找到一段关于这种事情的有趣对话: 为什么要避免在JavaScript中使用递增(“++”)和递减(“--”)运算符?


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