C语言有预增量和后增量的历史原因是什么?

(注意:我不是在询问预增量与后增量的定义,或者它们是如何在C / C ++中使用的。因此,我不认为这是一个重复的问题。)

C的开发人员(Dennis Ritchie等人)出于很好的理由创建了增量和减量运算符。 我不明白为什么他们决定创造前后增量/减量的区别?

我的感觉是,当C开发时,这些运算符比今天更有用。 大多数C / C ++程序员使用其中一种,而其他语言的程序员今天发现这种区别是奇怪和令人困惑的(注意:这完全基于轶事证据)。

他们为什么决定这样做,以及计算上发生了什么变化,这种区别今天没那么有用?

为了记录,可以在C ++代码中看到两者之间的差异:

int x = 3; cout << "x = 3; x++ == " << x++ << endl; cout << "++x == " << ++x << endl; cout << "x-- == " << x-- << endl; cout << "--x == " << --x << endl; 

将作为输出

 x++ == 3 ++x == 5 x-- == 5 --x == 3 

当时硬件广泛支持递增和递减1:单个操作码,并且速度很快 。 这是因为“递增1”和“递减1”是代码中非常常见的操作(直到今天)。

post和precrement表单仅影响在生成的机器代码中插入此操作码的位置。 从概念上讲,这模仿了“ 使用结果之前之后增加/减少”。 在一个声明中

 i++; 

没有使用’之前/之后’概念(因此它与++i;相同++i; ),但是

 printf ("%d", ++i); 

它是。 现在,这种区别与设计语言C时的区别同样重要(这个特定的习语是从它的前身“B”中复制而来的)。

从C语言的发展谈起

这个特征[PDP-7的“自动增量”存储单元“]可能会向Thompson [Ken Thompson,他设计的”B“,C的前身)建议这样的操作员。 使它们成为前缀和后缀的概括是他自己的。 实际上,自动增量单元并没有直接用于运算符的实现,并且创新的更强烈动机可能是他观察到++ x的翻译小于x = x + 1的翻译。

感谢@dyp提及此文档。

当你从n倒数时,无论是预先减量还是后减量都是非常重要的

 #include  void foopre(int n) { printf("pre"); while (--n) printf(" %d", n); puts(""); } void foopost(int n) { printf("post"); while (n--) printf(" %d", n); puts(""); } int main(void) { foopre(5); foopost(5); return 0; } 

查看在ideone上运行的代码 。

为了得到超出猜测的答案,你很可能不得不亲自询问Dennis Ritchie等人。

添加到已经给出的答案,我想补充两个可能的原因:

  • 懒惰/节约空间:

    您可以在输入文件中使用适当的版本保存一些击键/字节,例如while(--i) vs while(i--) 。 (看看pmg的答案看看,为什么两者都有所不同,如果你在第一次运行中没有看到它)

  • 美学

    出于对称性的原因,只有一个版本的前后增量/减量可能会让人觉得缺少某些东西。

编辑:在推测部分提供的输入文件中添加了少量字节,现在提供了一个非常好的“历史”原因。

无论如何,整理清单的主要观点是提供可能的解释的例子,这些解释不是太历史,但仍然在今天举行。

当然我不确定,但我认为除了个人品味之外,要求一个“历史性”的理由是从一个不必要的假设开始。

对于C.

让我们来看看Kernighan&Ritchie的原始理由(原始K&R第42页和第43页):

不寻常的方面是++和 – 可以用作前缀或后缀。 (…)在没有值的情况下(..)根据品味选择前缀或后缀。 但是htere是特别要求其中一个或另一个的情况。

本文继续介绍一些在索引中使用增量的示例,其明确目标是编写“ 更紧凑 ”的代码。 因此,这些运算符背后的原因是更紧凑的代码的便利性。

给出的三个示例( squeeze()getline()strcat() )仅使用索引在表达式中使用postfix。 作者将代码与不使用嵌入式增量的较长版本进行比较。 这证实了重点是紧凑性。

第10页的K&R重点介绍了这些运算符与指针解除引用(例如*--p*p-- )的结合使用。 没有给出进一步的例子,但同样,他们明确表示这种好处是紧凑的。

对于C ++

Bjarne Stroustrup希望具有C兼容性,因此C ++inheritance了前缀和后缀增量和减量。

但是还有更多内容:在他的书“ C ++的设计和演变 ”中,Stroustrup解释说,最初,他计划在用户定义的类中只有一个重载,后缀和前缀:

几个人,特别是Brian Kernighan,指出这种限制从C角度看是不自然的,并阻止用户定义一个可以用作普通指针替换的类。

这导致他找到当前的签名差异来区分前缀和后缀。

顺便说一下,没有这些运算符,C ++就不会是C ++而是C_plus_1 😉

考虑以下循环:

 for(uint i=5; i-- > 0;) { //do something with i, // eg call a function that _requires_ an unsigned parameter. } 

您不能使用预递减操作复制此循环,而无需在for(…)构造之外移动递减操作,并且最好在一个位置进行初始化,交互和检查。

一个更大的问题是:一个人可以为一个类重载增量运算符(全部4个)。 但是后来运算符严重不同:后期运算符通常会导致正在创建的类实例的临时副本,而前运算符则不会。 这在语义上是一个巨大的差异。

PDP-11有一条与*p++相对应的指令,另一条指令用于*--p (或者可能反之亦然)。