繁体   English   中英

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

[英]What are the historical reasons C languages have pre-increments and post-increments?

(注意:我不是在询问预增量与后增量的定义,或者它们在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 <stdio.h>
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 ++继承了前缀和后缀增量和减量。

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

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

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

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

考虑以下循环:

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

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

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

PDP-11有一条对应*p++指令,另一条对应*--p (或者可能反过来)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM