考虑以下代码:
a = 1
a = a + 1
print(a)
你写代码时候是不是也疑惑过,为什么代码中会有a = a + 1,这个怎么解释?这就不是在说1 = 2么?
这是一个对代码中"="常见的误解,"="应该表示相等,但是它在此真意义是赋值。
小编也认为这是不好的表示法。我也知道一些语言不会用a = a + 1写法,而是写成a:= a + 1。为什么不是规范起来呢?
通常的答案是"因为C"。但这只是一个小小的过失。为什么C这样做?让起来回顾下找下原因。
四位老哥
在上个世纪60年代早期,主要有四种主要的高级语言:COBOL,FORTRAN II,ALGOL-60和LISP。当时赋值过程要分成两步:1、定义变量时候初始化2、已有变量值改变时候重新赋值。以开头我们的Python例子,我们有:
a=1 #初始化
a = a + 1#重新分配
print(a)
人们没有使用这些特定的术语,但它捕捉到了每个人都在做的事情。以下是四种语言的操作符情,以及进行相等判断的检查:
ALGOL没有专门的初始化操作符。你先得创建一种类型的变量,然后使用运算符赋值给它。你可以做整数x;x:=5;但是你不能直接就x:= 5;,所以 FORTRAN是唯一一个用=做各种操作的语言,更像是现代语言的行为。但是我们知道历史上,C从ALGOL传承下来,所以:=赋值被删除了,并且把=作为了相等检查。
由ALGOL到CPL
ALGOL60可能是计算机科学史上最具影响力的编程语言,单也可能是最无用的语言。该语言按设计总核心规范中没有任何I/O特性。你可以对输入进行硬编码并测量输出,但如果你想用它做任何事情,你就得有一个扩展核心语言的编译器。 ALGOL是专为研究算法而设计的,你还想要做其他事情,它就会崩溃。
然而,它确实是一种强大的语言,人们希望将其推广,想在商业和工业使用。 1963年,克里斯托弗斯特拉奇和剑桥大学进行了第一次大推动。创建一个新语言CPL,CPL是在ALGOL之上增加了一大批创新,其中大部分是让我们后悔的特性。这其中一个就是初始化定义,其中一个变量可以在同一个语句中初始化并赋值!而不是写整数x;x:=5;你可以直接用x=5。
但是我们从:=演变到=,主要是因为CPL支持三种变量初始化:
=表示按值初始化。
表示通过引用初始化,以便如果xy,重新分配给x也会使y变异。但是如果你写了xy + 1并尝试重新分配x,程序就会崩溃。
≡表示通过替换来初始化,也就是使x成为一种在每次使用时都对RHS进行评估的niladic函数。他们从未解释如果你尝试重新分配给x可能会发生什么。
问题:现在=用于初始化和相等。幸运的是,CPL在实践中对此表现得很清晰,你要写=的任何地方你的意愿都明确无误的。
仅仅一年之后,Kenneth E. Iverson发明了APL,它将<-用于赋值。由于大多数键盘都不具备这一点,甚至连Iverson都没咋用过它,他的后续语言J则使用=:作为赋值。但是,APL深深地影响了S,S又深深地影响了R,这就是为什么<- 是首选的R赋值运算符。
由CPL到BCPL
CPL是一个很棒的语言,只有一个小问题,没有人能够实现它。少数人包揽了部分功能子集的部分实现,但对于那个年代的编译器来说,这太大而且太复杂了。因此,马丁理查德剥离了很多创造BCPL的复杂性。第一个BCPL编译器于1967年推出,并于1970年成为第一个CPL编译器。
除了其他简化之外,"三种初始化"规则被去掉了。Richards认为替代表达是适当的,可以被函数取代,并且与赋值相同。所以他把它们全部简化成一个=,但是命名全局内存地址,它使用了:。与CPL一样,=也是相等判断。对于重新赋值,他使用:=就像CPL和ALGOL一样。很多后来的语言遵循这个约定,=用于初始化,:=用于重新赋值,还有=用于相等判断。但是当Niklaus Wirth创造Pascal时它就成为主流,这就是我们现在称之为"Pascal风格"的原因。
在我记忆中,BCPL还是第一个"弱类型"语言,因为唯一的数据类型是数据字。这使得编译器更容易移植,导致更多的逻辑错误,但是Richards希望更好的流程改进和描述性命名能够抵消它。 另外,BCPL还引入了大括号作为定义块的一种手段。
由BCPL到 B
Ken Thompson希望将BCPL转换在PDP-7上运行。虽然BCPL有一个"小型编译器",但它仍然是PDP-7的最小工作存储器的四倍,因为PDP-7的存储器只有4k至16kb。所以Thompson需要创建一个新的,更小的语言。另外他个人审美上也希望尽量减少源代码中的字符数。这最终成为B设计的最大因素。这就是为什么我们有例如++和--操作符的初衷。
一旦你摆脱了命名的全局内存地址,BCPL总是使用=进行初始化,使用:=进行赋值。Thompson决定将它们合并为一个单一的标记,用于所有形式的任务,并且选择了=因为它更短。但是,这这样一来确定它的用意就有点含糊不清:如果x已经被声明,那么x=y是一个赋值还是一个相等性检查?而且在某些情况下,它应该是两者皆有!他添加了一个新的标记==,作为"等于"的唯一形式。正如Thompson所说的那样:
由于赋值的使用频率在一个典型程序中的大概是相等判断使用次数的两倍,所以操作符长度要减半才是最合适的。
Thompson(由Dennis Ritchie加入)在1969年左右发布了B语言的第一个版本。在同期两个人Ole Dahl和Kristen Nygaard发明了第一个OOP语言Simula 67。 Simula遵循ALGOL严格分离的初始化和赋值步骤的约定。Alan Kay也在这个时候也创造smalltalk,也遵循了同样的语法,还增加了块。因此,直到1971年左右,大多数新语言都使用:=进行赋值。
由B产生了C
剩下的就是大家比较熟悉历史。。。。。
还有一点需要补充的是,在随后的一年ML语言出现了,是第一真正强调纯函数式的语言。但它仍然在引用单元中有一个列外的设置,可以使用:=赋一个新值。从1980年开始,我们开始看到正确导向的命令式语言,特别是Eiffel和Ada,它们都使用:=来赋值。
从整体来看,=从来不是自然选择的赋值运算符。几乎ALGOL所有分支中的每个语言都使用:=来代替赋值,可能是因为=与等式相关联。现在大多数语言使用=,完全是因为C使用它,并且我们可以使用C向前追溯,是CPL第一个这样吃螃蟹的。