[英]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”的前体复制而来的)。
这个特征[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.