C语言有预增量和post-增量的历史原因是什么?
What are the historical reasons C languages have pre-increments and post-increments?
(注意:我不是在问预增量与post-增量的定义,或者它们在C/C++中的使用方式。因此,我不认为这个是一个重复的问题。)
C 语言的开发者(Dennis Ritchie 等人)出于非常充分的理由创建了递增和递减运算符。我不明白的是,为什么他们决定区分 pre- 和 post- increments/decrements?
我的感觉是,这些运算符在开发 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:单个操作码,快速。这是因为 "incrementing by 1" 和 "decrementing by 1" 是代码中非常常见的操作(直到今天)。
post- 和预递减形式仅影响此操作码在生成的机器代码中插入的位置。从概念上讲,这模仿了 "increase/decrease before or after using the result"。在单个语句中
i++;
未使用 'before/after' 概念(因此它与 ++i;
相同),但在
printf ("%d", ++i);
是的。这种区别现在和设计 C 语言时一样重要(这个特殊的习语是从它的前身 "B" 复制而来的)。
来自The Development of the C Language
This feature [PDP-7's "`auto-increment' memory cells"] probably suggested such operators to Thompson [Ken Thompson, who designed "B", the precursor of C]; the generalization to make them both prefix and postfix was his own. Indeed, the auto-increment cells were not used directly in implementation of the operators, and a stronger motivation for the innovation was probably his observation that the translation of ++x was smaller than that of x=x+1.
感谢@dyp 提及此文档。
PDP-11 有一条指令对应 *p++
,另一条指令对应 *--p
(或者可能相反)。
要获得超越推测的答案,很可能您必须亲自询问 Dennis Ritchie 等人。
除了已经给出的答案之外,我想补充两个可能的原因:
懒惰/保守space:
您可以使用 while(--i)
与 while(i--)
等构造中的适当版本在输入文件中保存一些击键/字节。 (看看 pmg 的回答,看看为什么两者都会有所不同,如果你在第一个中没有看到它 运行)
美学
出于对称性的原因,只有一个版本前增量/减量或后增量/减量可能会感觉缺少一些东西。
编辑:在提供的推测部分的输入文件中添加了几个字节,现在也提供了一个很好的 "historic" 理由。
无论如何,将列表放在一起的主要目的是给出可能的解释的例子,这些解释不太具有历史意义,但今天仍然有效。
当然我不确定,但我认为要求一个 "historic" 除了个人品味之外的理由是从一个不一定正确的假设开始的。
当你从n
开始倒数时,是预递减还是post-递减
非常重要
#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;
}
对于C
让我们看看 Kernighan & Ritchie 的原始理由(原始 K&R 第 42 和 43 页):
The unusual aspects is that ++ and -- may be used either as prefix or
as postfix. (...) In the context where no value is wanted (..) choose
prefix or postfix according to taste. But htere are situations where
one or the other is specifically called for.
本文继续提供一些在索引中使用增量的示例,其明确目标是编写“更紧凑”代码。所以这些运算符背后的原因是更紧凑的代码更方便。
给出的三个示例(squeeze()
、getline()
和 strcat()
)仅在使用索引的表达式中使用后缀。作者将代码与不使用嵌入式增量的较长版本进行了比较。这证实了重点是紧凑性。
K&R 在第 102 页突出显示,这些运算符与指针解引用结合使用(例如 *--p
和 *p--
)。没有给出进一步的例子,但他们再次明确表示好处是紧凑。
对于 C++
Bjarne Stroustrup 希望具有 C 兼容性,因此 C++ 继承了前缀和后缀递增和递减。
但还有更多内容:在他的书“C++ 的设计和演化”中,Stroustrup 解释说,最初,他计划只为后缀和前缀两者设置一个重载, 在用户定义中 类:
Several people, notably Brian Kernighan, pointed out that this
restriction was unnatural from a C perspective and prevented users
from defining a class that could be used as replacement for an
ordinary pointer.
导致他找到当前的签名差异来区分前缀和后缀。
顺便说一句,如果没有这些运算符,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(...) 构造之外,就无法使用预递减操作复制此循环,最好将初始化、交互和检查都集中在一个地方.
一个更大的问题是:可以为 class 重载增量运算符(全部 4 个)。但是这些运算符是截然不同的:post 运算符通常会生成 class 实例的临时副本,而预运算符则不会。这是语义上的巨大差异。
(注意:我不是在问预增量与post-增量的定义,或者它们在C/C++中的使用方式。因此,我不认为这个是一个重复的问题。)
C 语言的开发者(Dennis Ritchie 等人)出于非常充分的理由创建了递增和递减运算符。我不明白的是,为什么他们决定区分 pre- 和 post- increments/decrements?
我的感觉是,这些运算符在开发 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:单个操作码,快速。这是因为 "incrementing by 1" 和 "decrementing by 1" 是代码中非常常见的操作(直到今天)。
post- 和预递减形式仅影响此操作码在生成的机器代码中插入的位置。从概念上讲,这模仿了 "increase/decrease before or after using the result"。在单个语句中
i++;
未使用 'before/after' 概念(因此它与 ++i;
相同),但在
printf ("%d", ++i);
是的。这种区别现在和设计 C 语言时一样重要(这个特殊的习语是从它的前身 "B" 复制而来的)。
来自The Development of the C Language
This feature [PDP-7's "`auto-increment' memory cells"] probably suggested such operators to Thompson [Ken Thompson, who designed "B", the precursor of C]; the generalization to make them both prefix and postfix was his own. Indeed, the auto-increment cells were not used directly in implementation of the operators, and a stronger motivation for the innovation was probably his observation that the translation of ++x was smaller than that of x=x+1.
感谢@dyp 提及此文档。
PDP-11 有一条指令对应 *p++
,另一条指令对应 *--p
(或者可能相反)。
要获得超越推测的答案,很可能您必须亲自询问 Dennis Ritchie 等人。
除了已经给出的答案之外,我想补充两个可能的原因:
懒惰/保守space:
您可以使用
while(--i)
与while(i--)
等构造中的适当版本在输入文件中保存一些击键/字节。 (看看 pmg 的回答,看看为什么两者都会有所不同,如果你在第一个中没有看到它 运行)美学
出于对称性的原因,只有一个版本前增量/减量或后增量/减量可能会感觉缺少一些东西。
编辑:在提供的推测部分的输入文件中添加了几个字节,现在也提供了一个很好的 "historic" 理由。
无论如何,将列表放在一起的主要目的是给出可能的解释的例子,这些解释不太具有历史意义,但今天仍然有效。
当然我不确定,但我认为要求一个 "historic" 除了个人品味之外的理由是从一个不一定正确的假设开始的。
当你从n
开始倒数时,是预递减还是post-递减
#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;
}
对于C
让我们看看 Kernighan & Ritchie 的原始理由(原始 K&R 第 42 和 43 页):
The unusual aspects is that ++ and -- may be used either as prefix or as postfix. (...) In the context where no value is wanted (..) choose prefix or postfix according to taste. But htere are situations where one or the other is specifically called for.
本文继续提供一些在索引中使用增量的示例,其明确目标是编写“更紧凑”代码。所以这些运算符背后的原因是更紧凑的代码更方便。
给出的三个示例(squeeze()
、getline()
和 strcat()
)仅在使用索引的表达式中使用后缀。作者将代码与不使用嵌入式增量的较长版本进行了比较。这证实了重点是紧凑性。
K&R 在第 102 页突出显示,这些运算符与指针解引用结合使用(例如 *--p
和 *p--
)。没有给出进一步的例子,但他们再次明确表示好处是紧凑。
对于 C++
Bjarne Stroustrup 希望具有 C 兼容性,因此 C++ 继承了前缀和后缀递增和递减。
但还有更多内容:在他的书“C++ 的设计和演化”中,Stroustrup 解释说,最初,他计划只为后缀和前缀两者设置一个重载, 在用户定义中 类:
Several people, notably Brian Kernighan, pointed out that this restriction was unnatural from a C perspective and prevented users from defining a class that could be used as replacement for an ordinary pointer.
导致他找到当前的签名差异来区分前缀和后缀。
顺便说一句,如果没有这些运算符,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(...) 构造之外,就无法使用预递减操作复制此循环,最好将初始化、交互和检查都集中在一个地方.
一个更大的问题是:可以为 class 重载增量运算符(全部 4 个)。但是这些运算符是截然不同的:post 运算符通常会生成 class 实例的临时副本,而预运算符则不会。这是语义上的巨大差异。