早期的编程方式
- 程序如何进入计算机
- 打孔纸卡 Punched card
- 插线板 Plugboard
- 冯诺依曼架构 Von Neumann Architecture
- 面板编程 Panel programming
- 商业成功的计算机:Altair 8800
早期计算机如何编程?
打孔纸卡 -> 插线板 -> 面板拔开关。
计算机的原理:
- 怎么从内存读写数据,执行操作。比如把两个数字加在一起。
- 简单描述了指令的执行,也就是计算机程序。
程序如何“进入”计算机:
程序需要加载进内存,是计算机科学。(打孔纸卡,插线板,面板拔开关)
打孔纸卡 Punched card
需求:
给机器编程,早在计算机出现之前就有了,著名的就是纺织业。
如果只想一块红色大桌布,可以直接放红线进织布机,但是如果想要图案怎么办?比如条纹或方格。
需要没隔一会,调整一次织布机,因为非常消耗劳动力,所以图案纺织品很贵。
可编程纺织机:
每一行的图案由可穿孔纸卡决定,特定位置有没有穿孔,决定了线是高是低。
横线是从上/从下穿过,为了让每行图案不同,纸片连成长条,形成连续指令。
很多人认为雅卡尔织布机是最早的编程。
事实证明,穿孔纸卡便宜,可靠,也易懂。
后面世代中,穿孔纸卡用于1890年美国人口普查。
例如:一张卡存一个人的信息,比如种族,婚姻状况,子女数量,出生国家等等。针对每个问题,人口普查工作者会在对应位置打孔,当卡片插入汇总机,孔会让对应总和值+1,可以插入整个国家入口的卡片,在结束后得到各个总值。
但是,早期的汇总机不算计算机。因为它们只做一件事:汇总数据。操作是固定的,不能编程。穿孔 纸卡存的是数据,不是程序。之后,这些机器被加强,可以做减,乘,除。甚至可以做一些小决定,决定何时执行某指令。
插线板 Plugboard
为了正确执行不同计算,程序员需要某种控制面板,面板有很多小插孔,程序员可以插电线,让机器的不同部分,互相传数据和信号。因此也叫“插线板”。
但是,这意味着,运行不同程序要重新接线。所以到了1920年代,控制面板变成了可拔插。让编程更方便,可以给机器插入不同程序。比如,一个插线板算销售税,另一个算工资单。但给插线板编程很复杂。
用插线板编程,不只在机电计算机流行,世上第一台通用电子计算机,ENIAC,完成于1946年。用了一大堆插线板。
程序在纸上设计好之后,给ENIAC连线,最多可能花三个星期。因为早期计算机非常昂贵,停机几个星期只为了换程序,完全无法接受,急需要更快,更灵活的新方式来编程。
幸运的是,到1940年代晚期1950年代初。内存变得可行,价格下降,容量上升。与其把程序存在插线板,存在内存变得可行,这样易于修改,方便CPU快速读取。这类机器叫“存储程序计算机”。
冯诺依曼架构
如果内存足够,不仅可以存要运行的数据,还可以存程序需要的数据。包括程序运行时产生的新数据。
程序和数据都存在一个地方,叫“冯诺依曼结构”。将指令和数据混合存放到内存,穿孔纸片和插线板,都是分开。
冯诺依曼曾过:我在思考比炸弹重要得多的东西计算机。
冯诺依曼计算机的标志是,一个处理器(有算术逻辑单元)+ 数据寄存器 + 指令寄存器 + 指令地址寄存器 + 内存(负责存数据和指令)。
第一台冯诺依曼架构的“存储程序计算机”,由曼彻斯特大学于1948年建造完成,绰号“宝宝”。
虽然有内存很棒,但程序和数据,依然需要某种方式输入计算机,所以用穿孔纸卡。
到1980年代,几乎所有的计算机都有穿孔纸卡读取器,可以吸入一张卡片,把卡片内容写进内存,如果放了一叠卡片,读取器会一个个写进内存。一旦程序和数据写入完毕,电脑会开始执行。即便简单程序也有几百条指令,要用一叠纸卡来存。
用纸卡的最大型程序是美国空军的SAGE防空系统,于1955年完成,据称顶峰时期,雇佣了世上20%程序员。
主控制程序用了62500张穿孔纸片,等同于大约5MB的数据。穿孔纸卡不仅可以往计算机放数据,还可以取出数据,程序运行到最后,结果可以输到纸卡上,方式是打孔。然后人可以分析结果,或者再次放进计算机,做进一步计算。
穿孔纸卡的亲戚是纸带,基本是一回事,只不过更连续,不是一张张卡。
现在存储的方式,硬盘,只读光盘,DVD,U盘等等。
面板编程 Panel programming
与其插一堆线到插线板,可以用一大堆开关和按钮,做到一样的效果。面板上有指示灯,代表各种函数的状态和内存中的值。
50和60年代的计算机,一般都有这样巨大的控制台。很少有人只用开关来输入一整个程序,但技术上是可行的,早期针对计算机爱好者的家用计算机,大量使用了开关。因为大多数家庭用户负担不起昂贵的外围设备,比如穿孔纸卡读取器。
第一款取得商业成功的家用计算机是Altair 8800
有两种版本可以买:
- 预先装好的整机
- 需要组装的组件
为了给8800编程,要拨动面板上的开关。输入二进制操作码,然后按“存储键”把值存入内存。
然后会到下一次内存位置,可以再次拨开关,写下一个指令,重复这样操作。(初代plc 原型)
把整个程序都写入内存之后,可以推动开关,回到内存地址0,然后按运行按钮,灯会闪烁。
不管是插线板,开关或穿孔纸卡。早期编程都是专家活,不管是全职还是技术控,都要非常了解底层硬件。比如 操作码,寄存器等,才能写程序。所以编程很难,很烦。
哪怕工程师和科学家都无法完全发挥计算机的能力,需要一种更简单方式,告诉计算机要做什么。一种更简单的编程方式。
编程语言的发展史
编程:二进制 -> 助记符(汇编器) -> A-0(编译器) -> FORTRAIN
- 二进制写程序,先纸上写伪代码,手工转二进制。
- 用“助记符”写代码(LOAD 14)为了把助记符转二进制,汇编器诞生(Assembler)
- 哈佛1号计算机首批程序员,海军官员。(葛丽丝●霍普Grace Hopper)
- Grace 设计了编程语言A-0
- Grace 1952年做了第一个编译器(Compiler),实现A-0
- 变量(Variables)
- FORTRAIN
- COBOL
- 新语言
1960年代:ALGOL, LISP, BASIC
1970年代:Pascal C, Smalltalk
1980年代:C++, Object-C, Perl
1990年代:Python, Ruby, Java
组成计算机的物理组件:
比如电
,电路
,寄存器
,RAM
, ALU
, CPU
。
在硬件层面编程非常麻烦,所以程序员想要一种更通用的方法编程。
一种“更软的”媒介: 软件。
第一条指令在内存地址0: 0010 1110
。前4位操作码,简称OPCODE
对于这个假设CPU,0010
代表LOAD A
指令:把值从内存复制到寄存器A。
后4位是内存地址,1110
是十进制的14。
所以这8位表达的意思是:读内存地址14,放到寄存器A。
只是用了两种不同语言,可以想成是英语和摩尔斯码的区别。
“hello
” -> “.... . .-.. .-.. ---
”是一个意思:“hello
”
只是编码方式不同,英语和摩尔斯码的复杂度也不同,英语有26个字母以及各种发音。摩尔斯码只有“点”和“线”但它们可以传达相同的信息,计算机语言也类似。
二进制写程序
计算机能处理二进制,二进制是处理器的“母语”。事实上,它们只能理解二进制。这叫“机器语言”或“机器码”。
在计算机早期阶段,必须用机器码写程序。
具体来讲,会先在纸上写一个“高层次版”的伪代码(PSEUDO-CODE)。
例如:从内存取下一个销售额,然后加到天,周,年的总和,然后算税。
写好伪代码后,用“操作码表”把伪代码转成二进制机器码,翻译完成后,程序可以喂入计算机并运行。
助记符
在1940~1950年代,程序员开发出一种新语言,更可读,更高层次。
每个操作码分配一个简单名字,叫“助记符”(MNEMONICS)。
“助记符”后面紧跟数据,形成完整指令。与其用1和0写代码,程序员可以写“LOAD A 14”。它不能理解文字,只能理解二进制,所以程序员想了一个技巧,写二进制程序来帮忙。它可以读懂文字指令,自动转成二进制指令。这种程序叫“汇编器”。
汇编器读取用“汇编语言”写程序,然后转成“机器码”。“LOAD_A 14”是一个汇编指令的例子。
随着时间的推移,汇编器有越来越多功能,让编程更容易,其中一个功能是自动分析JUMP
地址。
例如:JUMP NEGATIVE
指令跳到地址5, JUMP 2
指令跳到地址2。
问题是,如果在程序开头多加一些代码,所有地址都会改变。更新程序会很痛苦。
所以汇编器不用固定跳转地址,而是让插入可跳转的标签。
当程序被传入汇编器,汇编器会自己搞定跳转地址。程序员可以专心编程,不用管底层细节。隐藏不必要的细节来做更复杂的工作。
然而,即使汇编器有这些厉害的功能,比如自动跳转。汇编只是修饰了一下机器码。
一般来说,一条汇编指令对应一条机器指令。所以汇编码和底层硬件的连接很紧密,汇编器仍然强迫程序员思考,用什么寄存器和内存地址。
如果突然要一个额外的数,可能要修改很多代码。
A-0
哈佛1号在1944年战时完成,帮助盟军作战,程序写在打孔纸袋上,放进计算机执行,如果程序由漏洞,直接使用胶带来补“漏洞”。
Mark 1的指令集非常原始,甚至没有JUMP
指令,如果代码要跑不止一次,得把带子的两端连起来,做成循环。(循环,物理,物理,循环)
给Mark 1编程简直是噩梦,战后,霍普继续研究计算机,为了释放电脑的潜力,设计了一个高级编程语言,叫“算术语言版本 0”,简称“A-0”。
汇编与机器指令是一一对应的,但一行高级编程语言,可能会转成几十条二进制指令。为了做到这种复杂的转换,霍普在1952年创造了第一个编译器,编译器专门把高级语言,转成低级语言,比如汇编或机器码(CPU 可以直接执行机器码)
尽管“使编程更简单”很诱人,但是很多人持怀疑态度。
霍普曾说:“我有能用的编译器,但没人愿意用,他们告诉我计算机只能做算术,不能运行程序”。
不久,很多人尝试创造新编程语言,到现代如今有上百种语言。
可惜的是,没有任何A-0
的代码遗留下来。
使用Python举例:
假设想相加两个数字,保存结果,记住,如果用汇编代码,得从内存取值,和寄存器打交道,以及其它底层细节。
不用管寄存器的内存位置,编译器会搞定这些细节,不用管底层细节。
程序员只需要创建,代表内存地址的抽象,叫“变量”(给变量取名字)。
底层操作时,编译器可能把变量A存在寄存器A,但程序员不需要知道这些,眼不见心不烦。
FORTRAN
但A-0
和之后的版本没有广泛使用。FORTRAN
,名字来自“公式翻译”。这门语言数年后由IBM在1957年发布。主宰了早期计算机编程。
FORTRAN
项目总监John Backus
曾说:我做的大部分工作都是因为懒,我不喜欢写程序,所以我写这门语言,让编程更容易。
FORTRAN
写的程序,比等同的手写汇编代码短20倍,然后FORTRAN
编译器会把代码转成机器码。
对于FORTRAN
程序的性能是否比得上手写代码?但因为能让程序员写程序更快,所以成了一个更经济的选择。
运行速度慢一点点,编程速度大大加快。
当时IBM在卖计算机,因此最初的FORTRAN
代码只能跑在IBM计算机上,1950年代大多数编程语言和编译器,只能运行在一种计算机上。如果升级电脑,可能要重写所有代码。因此工业界,学术界,政府的计算机专家,在1959年组建了一个联盟: 数据系统语言委员会,霍普担任顾问。开发一种通用的编程语言,可以在不同机器上通用。最后诞生了一门高级,易于使用:普通面向商业语言,简称COBOL
。
为了兼容不同底层硬件,每个计算机架构需要一个COBOL
编译器,最重要的是,这些编译器都可以接受相同的COBOL
代码。不管是什么电脑,这叫“一次编写,到处运行”。如今的大多数编程语言都是这样。不必接触CPU特有的汇编码和机器码。
减下了使用门槛,在高级编程语言出现之前,编程只是计算机专家和爱好者才会做的事,而且通常是主职。但现在,科学家,工程师,医生,经济学家,教师等等,都可以把计算机用于工作。
“解释型语言”对应“解释器”,编译器是把语言编程二进制代码再运行。
计算机科学从深奥学科,变成了大众化工具,同时,编程的抽象也让计算机专家,制作更复杂的程序。
1959年代,编程语言黄金时代开始,和硬件一起飞速发展。
1960年代:ALGOL, LISP, BASIC
1970年代:Pascal C, Smalltalk
1980年代:C++, Object-C, Perl
1990年代:Python, Ruby, Java
2000年代:Swift, C#, Go
新语言想用更聪明的抽象,让某些方面更容易或更强大,或利用新技术和新平台带来的优势。
编程原理-语句和函数
- 变量,赋值语句
- Grace Hopper 拍虫子游戏
- if 判断
- while循环
- for 循环
- 函数
用机器码写程序,还要处理那么多底层细节,对写大型程序是个巨大障碍。为了脱离底层细节,开发了编程语言,让程序员专心解决问题,不用管硬件细节。
变量,赋值语句
规定句子结构的一系列规则叫语法。
a=5
是一个编程语言语句,意思是创建一个叫a的变量,把数字5放里面。这叫“赋值语句”,把一个值赋给一个变量。为了表达更复杂的含义,需要更多语句。
变量名可以随便取。
程序由一个个指令组成,有点像菜谱:烧水,加面,等10分钟,捞出来就可以吃了。
程序也是这样,从第一条语句开始,一句一句运行到结尾。
拍虫子游戏
阻止虫子飞进计算机造成故障,关卡越高,虫子越多。在虫子损坏继电器之前,抓住虫子。
开始编写时,需要一些值,来保存游戏数据,比如当前关卡数,分数,剩余虫子数,还剩下几个备用继电器。
所以要“初始化”变量,“初始化”的意思是设置最开始的值。
level = 1 # 关卡=1
score = 0 # 分数=0
bugs = 0 # 虫子=5
relays = 4 # 备用继电器=4
name = 'andre' # 玩家
为了做成交互式游戏,程序的执行顺序要更灵活,不只是从上到下执行,因此用“控制流语句”。if
构成的语句叫: “条件语句”
如果希望根据条件执行多次,需要“条件循环”。
计算指数,代码很常用。如果每次想用就复制粘贴,会很麻烦,每次都要修改变量名,如果代码发现问题,要补漏洞时,要把每一个复制粘贴过的地方都找出来修改,而且会让代码更难懂,少即是多。想要某种方法,把代码“打包”,可以直接使用,得出结果。不用管内部复杂度。
函数
为了隐藏复杂度,可以把代码打包成“函数”也叫“方法”或“子程序”,其它地方想用到这个函数,直接写函数名就可以了。需要把结果,交给使用这个函数的代码,所以用return
语句,指明返回什么。
函数是,抽象的力量。
如今超过100行代码的函数很少见,如果多于100行,应该有些东西可以拆出来做成一个函数。
如果有第二次地方使用,就抽象出函数。
模块化编程,不仅可以让单个程序员独立制作app,也让团队协作可以写更大型的程序。
现代编程语言,有很多预先写好的函数集合,叫“库”。
由专业人员编写,不仅效率高,而且经过了仔细的检查。
几乎做所有的事情,都有库,网络,图像,声音。