- 「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」。
- 当然 不论新老朋友 我相信您都可以 从中获益。如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力!
知乎问题:如何让不懂编程的人感受到编程的魅力?
Part 0. 前言
问一个类似的问题:「如何让不懂篮球的人感受到篮球的乐趣?」
很明显,答案取决于人。
对于某些人来说,编码可能是乏味而艰巨的,但对于有些人则是非常有趣且有益的。
通常情况下,编写好代码之后,我们很难评判编码的过程是无聊还是有趣的。
但是我们可以通过更好地了解 「计算机编程是什么」,因此您可以自己来评判这是否让你感到乐趣。
就当代的发展水平来说,只要有电的东西就会涉及到编码;
Part 1. 计算机发展简史 | 解决实际的问题
一切的开端 | 织布机
衣食住行是人类的基本需求,世界各地都有纺织和织机的发明。
如何提升纺织的效率,成为一个很重要的课题。
与 「针织物」 不同,「机织物」 由两条或两组以上的相互垂直的两个系统纱线或丝线构成,纵向的纱线 叫 经纱,横向的纱线 叫 纬纱。
通过两条线不同规律的交错,就会形成不同的颜色和排列的变化,也就会带来不同的纹样。
最原始的织机是手动完成的。
后来人们发现,织物纹样的变化总是按照一定规律排列完成的,到了战国时期,就发明了 多综式提花织机,通过 综框 来完成这一项工作:
[图片上传失败...(image-a35874-1594826100047)]
这或许就是最原始的 编码:通过把 提起规律相同的经纱 穿入 同一个综框的综丝 中,来达到当某一个综框提起时 (通过脚踏板完成),表达某一个特定纹路的线被提起,也就完成了 特定纹路的编织,加快了工作效率。
织布机的更进一步 | 束综提花织机
不过采用综框也有一个明显的限制,那就是 无法织出比较复杂的纹样,因为纹样复杂则代表着需要更多的经纬纱以及经纱提升的规律更复杂,意味着可能引入成百上千次纬纱才能完成一个循环。
如果仍然采用综框控制纱线提升,则可能需要成百上千个综框,这在机械上实现是非常困难的,因此便有了 束综提花织机。
束综提花织机没有综框,而是被 设计成两层,每一根经纱会穿入综丝中实现 单独的控制,上层 的人将需要提起的经纱提起,而 下层 的人则再经纱提起后通过梭子将纬纱送入织口,并用打纬装置将引入的纬纱打牢。
然而通常来说,这类织机上会有成千上万根纬纱,纹样复杂,于是聪明的老祖宗们发明了 花本:
简单来说,花本存储了纹样信息。
图中花本的竖线连接穿入了经纱的综丝,横线存储了每一次引入纬纱时提花信息,当竖线越过横线覆盖在横线前方时,表明对应的经纱要被提起。
这就有点儿 编码规则 的意思。
会说话的「机器」 | 雅卡尔织布机
上面说到的束综提花织机虽然是一大进步,但可想而知的是,它仍然效率缓慢并且织布工人的劳动量非常大,也非常辛苦。
时间来到 18
世纪的欧洲。1725
年,布乔 开拓性的使用 打孔纸带 来控制经线的提起和放下,从而让织出花样成为一种半自动化的工作:
历史上第一次,机器能够读出存储介质中的内容,并且照其行事。
在布乔提出构想 65
后的 1790
年,约瑟夫·玛丽·雅卡尔 根据前人的成果设计了新式织机,最终于 1805
年完成了首台 自动提花织机:
雅卡尔将 穿孔纸带 改进为 穿孔卡片,根据纹样图案在卡片上打孔,通过孔的有无 带动一系列机械运动装置来 控制经纱的提升,一张卡片对应循环内一次引纬时经纱提升的信息,引纬完成后,可通过脚踏板控制卡孔卡片转动,下一张卡片翻转至工作位置以控制新一次引纬的提花:
[图片上传失败...(image-547502-1594826100047)]
雅卡尔织机大幅度节省了时间和工作量 (全自动且效率是之前的二十五倍),而且只需一位工人,很快就被广泛使用在工厂生产中,雅卡尔也荣获了拿破仑授予的荣誉勋章。
穿孔卡片控制织物纹样的设计成为了程序设计思想的萌芽,为信息技术的发展开展了一条新的道路。
程序设计思想开始萌芽 | 差分机
时间来到 19
世纪初,法国人 巴贝奇 (Chanles Badbbage) 在贾卡织机的启发下,设计并制造了 差分机。
故事背景
18
世纪末,法国政府在开创米制之后,决定在数学中统一采用十进制,竟奇葩地想把原本 90
度的直角划分成 100
度、把原本 60
秒的 1
分钟划分成 100
秒,尽管从现在看来这样的想法绝逼是一种倒退,但他们在当时真就实施了。这一改制带来的不光是人们在使用时直观上的别扭,原本制作好的数学用表 (如三角函数表) 都需要全部重制。
法国政府将这项丧心病狂的工程交给了 数学家普罗尼 (Gaspard de Prony),普罗尼正头疼着要如何才能完成这项艰巨的任务,突然想起著名经济学家 亚当·斯密 (Adam Smith) 的那本《富国论》,他决定采用书中提出的 劳动分工 的做法,将制表的工作人员分成三组:
- 第一组 由五六名牛逼的数学家组成,他们负责制定运算中所需的公式;
- 第二组 由九到十个擅长数学的人组成,他们负责计算出一些关键数据,并把第一组制定好的公式进行简化;
- 第三组 由约一百名计算人员组成,他们利用第二组提供的关键数据和公式,做最简单的加减操作就能得出最终结果。
第三组的工作简单到什么程度,就是他们甚至都不知道自己正在算什么玩意儿,事实上他们的文化程度大部分都不高,里头好多都是理发师、失业人员什么的。可见即便文盲都能完成的计算,在那个时代还是得依靠人力去做。
而为了保证用表的正确性,普罗尼要求 每个数至少算两遍,并且 要在法国的不同地点用不同的方法计算。这项劳民伤财的工程整整进行了十年才完成,然而不幸的是,最终的表里仍然有错。说到这一点,可以说,那个时代基本没有一版数学用表是完全正确的,有些版本甚至错误百出,要知道数学用表出错有时后果会很严重,比如航海表一出错就可能直接导致船毁人亡。
巴贝奇 在了解到普罗尼的事迹后泪流满面,决心要做一套完全正确的数学用表,为达目的,他尝试了各种减少错误的手段,比如调整纸张和墨水的颜色以提高数字的识别度,直接拿现有的多个版本的表进行誊抄、比对、让不同人员反复校对,在 1827
年出版了一个版本,结果里头还是有错。只要是人为的就没有完美的,巴贝奇彻底跪了,他发誓要造一台机器,让机器去生产数学表。
这就是史上著名的 差分机 了。
伦敦科学博物馆·差分机设计图纸&半成品:
第一台真正意义上的电脑 | 分析机
尽管没能亲手实现差分机,但巴贝奇并不会气馁,或者说他本来就是根本停不下来的那种人。明知实现不了,巴贝奇仍在一刻不停地改进着自己的设计,直到有一天,他构思出了一种空前的机器——分析机,正式成为现代计算机史上的第一位伟大先驱。 (Father of computing)
1834
年,分析机概念诞生之际,巴贝奇自己都为之感到无比震惊。在此之前,任何一台计算机器都只能完成其被预定赋予的计算任务,要么是简单的加减乘除,要么像差分机那样只能做差分运算,它们都属于 calculator
,而分析机才是真正的 computer
,它不局限于特定功能,而竟然是可编程的,可以用来 计算任意函数——现代人无论如何也无法想象在一坨齿轮上写程序是怎样一种体验吧!
巴贝奇设计的分析机主要包括三大部分:
- 用于存储数据的计数装置,巴贝奇称之为 “仓库”(store),相当于现在 CPU 中的存储器,这部分是从差分机上的计数装置改进而来的,我们很容易想象它的模样;
- 专门负责四则运算的装置,巴贝奇称之为 “工厂”(mill),相当于现在 CPU 中的运算器,这部分的结构相对复杂,巴贝奇针对乘除法还做了一些优化;
- 控制操作顺序、选择所需处理的数据和输出结果的装置,巴贝奇没有起名字,由于其呈桶状,我们可以叫它 “控制桶”,控制桶显然相当于现在 CPU 中的控制器。
以上三部分,加上巴贝奇并没有疏漏的输入输出设备,我们惊讶地发现,分析机的组成部分和现在冯·诺依曼架构所要求的五大部件一模一样!
巴贝奇另一大了不起的创举就是将 穿孔卡片(punched card) 引入了计算机器领域,用于控制数据输入和计算,从那时起,到第一台电子计算机诞生为止,期间几乎所有的数字计算机都使用了穿孔卡片。
[图片上传失败...(image-14c434-1594826100047)]
整个分析机就是在类似这样的齿轮和拉杆作用下实现可编程运算的:先从数据卡片读入数据到存储器,再将存储器中的数据传输到运算器,运算器算完后又将数据传回存储器。
可惜的是,巴贝奇穷其一生也没能真正把分析机做出来,留给后世的又是一台模型机和两千多张图纸,以及这样一段遗言:
「如果一个人不因我一生的借鉴而却步,仍然一往直前制成一台本身具有全部数学分析能力的机器……那么我愿将我的声誉毫不吝啬地让给他,因为只有他能够完全理解我的种种努力以及这些努力所得成果的真正价值。」
伦敦科学博物馆·分析机设计图纸&模型机:
穿孔时代的到来 | 制表机
从 1790
年开始,美国每 10
进行一次人口普查。百年间,随着人口繁衍和移民的增多,从 1790
年的 400
万不到,到 1880
年的 5000
多万,人口总数呈爆炸式地增长。
不像现在这个的互联网时代,人一出生,各种信息就已经电子化、登记好了,甚至还能数据挖掘,你无法想象,在那个计算设备简陋得基本只能靠手摇进行四则运算的 19
世纪,千万级的人口统计就已经成了当时政府的 “不能承受之重”。
1880
年开始的第 10
次人口普查,历时 8
年才最终完成,也就是说,他们在休息两年之后就要开始第 11
次普查了,而这一次普查,需要的时间恐怕要超过 10
年,那第 12
次、13
次呢?本来就是 10
年一次的统计,如果每次耗时都在 10
年以上,这件事情就变得没有意义了。
这可愁煞了当时的人口调查办公室,他们决定面向全社会招标,寻求能减轻手工劳动、提高统计效率的发明。正所谓机会都是给有准备的人的,一位毕业于哥伦比亚大学的年轻人 赫尔曼·霍尔瑞斯 (Herman Hollerith) 带着他在 1884
年申请的专利从众多方案中脱颖而出。
制表机
他发明的机器叫 制表机 (tabulator/tabulating machine),顾名思义,就是专门用来制作数据统计表的机器。制表机主要由示数装置、穿孔机、读卡装置和分类箱组成。
示数装置包含 4
行、10
列共 40
个示数表盘,每个盘面被均匀地分成 100
格,并装有两根指针,和钟表十分相像,“分针” 转一圈可计 100
,“时针” 转一圈则计 10000
。可见,整个示数装置可以表达很庞大的数据。
制表机的工作是围绕穿孔卡片展开的:操作员先使用穿孔机制作穿孔卡片,再使用读卡装置识别卡片上的信息,机器自动完成统计并在示数表盘上实时显示结果,最后,将卡片投入分类箱的某一格中,进行分类存放,以供下次统计使用。
穿孔卡片的应用
此前的某一天,霍尔瑞斯正在火车站排队检票,目光不经意落到检票员手中咔咔直响的打孔机上。他发现,检票员会特意根据乘客的性别和年龄段,在车票的不同地方打孔。越来越多的人过检,他进一步确认了这个规律。一个灵感朝他袭来:如果有一张更大的卡,上面有更多的位置可以打孔,就可以用来表示更多的身份信息,包括国籍、人种、性别、生日等等。
这就是用在 1890
年人口普查中的穿孔卡片,一张卡片记录一个居民的信息。卡片设计长约 18.73cm
,宽约 8.26cm
,正好是当时一张美元纸币的尺寸,因为霍尔瑞斯直接用财政部装钱的盒子来装卡片。
卡片设有 300
多个孔位,与雅卡尔和巴贝奇的做法一样,靠每个孔位打孔与否来表示信息。尽管这种形式颇有几分二进制的意味,但当时的设计还远不够成熟,并没有用到二进制真正的价值。举个例子,我们现在一般用 1
位数据就可以表示性别,比如 1
表示男性,0
表示女性,而霍尔瑞斯在卡片上用了两个孔位,表示男性就其中一处打孔,表示女性就在另一处打孔。其实性别还凑合,表示日期时浪费得就多了,12
个月需要 12
个孔位,而常规的二进制编码只需要 4
位。当然,这样的局限也与制表机中简单的电路实现有关。
细心的读者可能发现卡片的右下角被切掉了,那不是残缺,而是为了避免放反而专门设计的,和现在的二维码只有 3
个角是一个道理。
这类实用的细节设计在穿孔机上表现得更为出色。下图为一位操作员正在使用穿孔机给卡片打孔的情景,她并不需要在卡片上吃力地搜寻孔位,而是直接对着孔距更大的操作面板打孔,一根杠杆将两者的孔位一一对应。操作面板还做成了弧形,颇有一分如今人体工程学键盘的风姿。
在制表机前,穿孔卡片(或纸带)多用于存储指令而不是数据。比较有代表性的,一是雅卡尔提花机,用穿孔卡片控制经线提沉;二是自动钢琴,用穿孔纸带控制琴键压放。美剧《西部世界》中,每次故事循环的开始,都会给一个自动钢琴的特写,弹奏起看似宁静安逸、实则诡异违和的背景乐。
是霍尔瑞斯将穿孔卡片作为 数据存储介质 开来,并开启了一个崭新的 数据处理纪元。后来人们也把这类卡片称为霍尔瑞斯卡片,穿孔卡片和穿孔纸带作为输入输出载体,统治了计算领域整整一个世纪。
单元记录时代
在制表机的高效运转下,1890
年的人口普查只花了 6
年时间。1896
年,霍尔瑞斯成立制表机公司(The Tabulating Machine Company)并不断改进自己的产品,先后与英国、意大利、德国、俄罗斯、澳大利亚、加拿大、法国、挪威、美国波多黎各、古巴、菲律宾等多个国家和地区合作开展了人口普查。
到 1914
年,制表机公司每天生产的穿孔卡片多达 200
万张。不多久,一些竞争对手逐渐起家,历史迎来了繁荣的数据处理时代。它们的产品也不再局限于人口普查,逐渐扩展到会计、库存管理等一些同样需要跟大数据打交道的领域,这些机器作为制表机的后裔被统称为单元记录设备(unit record equipment)。
围绕穿孔卡片的制卡、读卡、数据处理和卡片分类是它们的标准功能,穿孔机、读卡器、分类器是它们的标准配置。这些部件的自动化程度越来越高,比如手动的读卡装置很快被自动读卡机所取代,读卡速度从每分钟 100
张逐步提高至每分钟 2000
张。随着识别精度的提高,卡片的孔距也越来越小,具有 80~90
列孔位的卡片成为主流,有些卡片的孔位甚至多达 130
列。
机器的功能也逐渐强大,不再只是简单地统计穿孔数目,减法、乘法等运算能力陆续登场。1928
年,哥伦比亚大学的科学家们甚至用单元记录设备计算月球的运行轨迹,他们在 50
万张卡片上打了 2000
万个孔,彰显着单元记录设备的无限潜力。
机器的电路实现越来越复杂,但同时也越来越通用。1890
年所用的那台制表机的 线路是固定的,遇到新的统计任务,改造起来十分麻烦。
1906
年,霍尔瑞斯便引入了接插线板(plugboard)——一块布满导电孔的板卡,可通过改变导线插脚在板上的位置改变线路逻辑。试想一下,接插线板的内部已经布好了具有各种功能的线路,但它们都处在断开状态,各自连接着接插线板上的某两个孔位,像一窝嗷嗷待哺的小鸟长大着嘴巴,外部的导线就像美味的虫子,当虫子的头尾分别与小鸟的上喙和下喙接触,线路就被导通,这只小鸟就开始工作了。如此,每次使用就可以激活不同的 “小鸟”,从而完成不同的任务。这已经是一种可编程性的体现。
1911
年,制表机公司与另外 3
家公司合并成立 CTR 公司 (Computing-Tabulating-Recording Company),制表机公司作为其子公司继续运营到 1933
年。
1924
年,CTR 更名为 国际商业机器公司(International Business Machines Corporation),就是现在大名鼎鼎的 IBM 公司。可见,在如今众多年轻的 IT 公司中,拥有百年历史的 IBM 是位当之无愧的前辈,它完整地参与和见证了整个现代计算机的发展史。IBM 保持了制表机公司在单元记录市场的龙头地位,到 1955
年,其每天生产的穿孔卡片多达 7250
万张。
1937
年开始,单元记录设备逐步电子化,与电子计算机的界线渐渐模糊,并最终为后者让路。随着 1976
年 IBM 一型最核心的单元记录产品的停产,短暂的单元记录时代也宣告谢幕,它仿佛是电子计算时代来临前的预演和铺垫,许多设计被沿用下来,比如穿孔卡片和接插线板。
有趣的是,即使电子计算机逐渐普及,许多机构由于用惯了单元记录设备,迟迟不愿更换,少数机构甚至一直用到了 21
世纪。
一句话总结
编程能够帮助我们解决一些非常实际的问题,用一种非常酷的方式。
感兴趣也可以扩展阅读一下:改变世界的代码行
Part 2. 二进制和 CPU 原理 | 编码中蕴藏的智慧
原来,我们是这样计数的
在讨论「二进制」和「CPU 如何工作」之前,我们先来讨论一下我们生活中最稀疏平常的 数字,我们与之频繁地打交道:一个约定的时间、一件商品的价格、一个人的身高....却很少有人细细想过,这些数字是如何表达出来的?为什么你理所当然地把 1024
理解为「一千零二十四」而不是别的含义?
也许你从未想过,在这简单的记数中,沉淀着人类的大智慧。
一进制计数法
早在数字的概念产生之前,人类就学会了使用树枝、石子、贝壳等自然界随处可见的小物件表示猎物的、果实的、部落人口的数量。比如在某个角落堆上一堆石子,每打到 1 只猎物,就扔 1 颗石子进去,每吃掉 2 只猎物,就从中取走 2 颗石子。他们并不在意石子的总数,只是时不时地瞅一眼,心底大致有数。
其实这是一种最朴素的记数方式,数学家称之为 一进制记数法(unary numeral system)。我们把它符号化一下,比如用斜杠 /
来表示:
-
1
就是/
; -
2
就是//
; -
4
就是////
;
好像没毛病,我们平时掰手指用的就是这种记数法,但数字一大,场面就要失控了。
符值相加记数法
为了解决记录大数的问题,于是我们得发明一些其他符号来表示更大的数值,比如用横杠 -
表示 10
,用十字 +
表示 100
。那么:
-
16
就是-//////
; -
32
就是---//
; -
128
就是+--////////
;
漂亮....这种靠符号类型和符号数量表示数字的方法被称为 符值相加记数法(sign-value notation),古埃及和古罗马用的都是它,只不过符号各不相同。
古埃及的记数符号:
1 | 10 | 100 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|---|---|
1024
在古埃及就写作:
你会发现,符值相加记数法的一大优点是,符号的顺序可以任意打乱,数字含义不受影响。我国藏族曾用石子表示 1
、木棍表示 10
、果核表示 100
、蚕豆表示 1000
、瓦片表示 10000
,那么,当你把 1
颗蚕豆、2
根木棍和 4
颗石子胡乱地攥在手里,别人依然知道它们是 1024
。
古罗马的做法略有不同,他们对五进制情有独钟:
1 | 5 | 10 | 50 | 100 | 500 | 1000 |
---|---|---|---|---|---|---|
I | V | X | L | C | D | M |
这些符号沿用至今,想必大家(至少对前 3
个)都比较熟悉,许多钟表仍保留着使用罗马数字的习惯,1~12
分别表示为:I
、II
、III
、IV
、V
、VI
、VII
、VIII
、IX
、X
、XI
、XII
。你会发现,罗马记数法是符值相加记数法的变种,因为它不光「相加」,还「相减」。这种方式就不允许符号乱序了,IV
和 VI
表示的是不同的数字。
那罗马人何苦要使用这种更复杂的记数法呢?无非是为了读写方便。同样表示 9
,IX
比 VIIII
更简洁。
其实有一种更好使的方法——用另外一些列符号来表示符号的数量。比如用 A
表示 1
个符号,用 B
表示 2
个符号,以此类推,用 I
表示 9
个符号。
如此,上文表示 256
的 ++-----//////
就可以写作 B+E-F/
。你一定感觉莫名其妙,这种写法哪里方便了。其实中文的数字表示就是这种形式,只不过我们用得太习惯了,以至于没有发现。
在中文中,个
、十
、百
代替了 /
、-
、+
,而 一
、二
、三
代替了 A
、B
、C
。256
就写作 二百五十六个
,个
比较累赘,我们通常把它省略了。
其实像日语、英语用的也同样是这种记数法,简洁、优雅。
美中不足的是,这种形式虽便于读写,却不便于计算。中国古人为算筹和算盘这类经典算具搭建起广阔的舞台,却没给笔算留出一席之地。想象一下,如果让你把这些汉字写在草稿纸上,列个竖式,你的内心一定非常别扭。
位值制记数法
公元5世纪,印度数学家阿耶波多(Aryabhata 476–550)创立了现在广泛使用的 位值制记数法(positional notation/place-value notation),该记数法使用的主要符号,是同为印度人发明的阿拉伯数字:0
、1
、2
、3
、4
、5
、6
、7
、8
、9
。
与符值相加记数法类比,位值制中的 1
、2
、3
代替的是 A
、B
、C
,那 /
、-
、+
呢?是 靠阿拉伯数字的位置来表示的。众所周知,最右位相当于 /
,次右位相当于 -
。靠每个位置上的数值来表示数字,故名位值制。
严谨的数学家用一种多项式高度概括了位值制记数法的本质,在十进制中,这个多项式是这样的:
这是一个 n
位十进制数,ai 就是第 i 位上的数值。为便于直观理解,举个 1024
的例子吧:
由于我们熟悉了十进制,这样费心费力的展开可能会让你觉得好笑,但当我们把它推广到其他进制时,这个多项式的价值就体现了出来。n 位 b 进制数的位值制表示:
1024
用二进制怎么表示?
因此,1024
的二进制写作 10000000000
。
除了最普遍的十进制和计算机中的二进制,常见的还有七进制(如 1
周 7
天)、十二进制(如 1
年 12
个月)、十六进制(如古代 1
斤 16
两)、六十进制(如六十甲子)等等,只要有意义,任何进制都可以为你所用。
为什么使用二进制
至此,你对「二进制」或许不再那么陌生,它仅仅是数制的一种而已。
可为什么一定是二进制呢?使用人类习惯的十进制不好吗?
理由一:物理上易于实现
计算机依靠电力工作,这也就意味着需要将数字信号映射到电信号,实现这种映射最简单的方法是:
- 0 - 没有电(0 V)
- 1 - 有点(5 V)
二进制在技术上最容易实现。这是因为具有两种 稳定状态 的物理器件很多,如门电路的导通与截止、电压的高与低等,而它们恰好可以对应表示 “1” 和 “0” 这两个数码。假如采用十进制,那么就要制造具有 10
种 稳定状态 的物理电路,而这是非常困难的。
理由二:机器可靠性高
为什么使用更复杂的数字系统是一个问题?
假设我们使用三元(3 位数字)数字系统涉及计算机,如果我们具有从 0 V
到 5 V
的电压,那么我们可以进行以下的映射:
- 0 - 0 V;
- 1 - 2.5 V;
- 2 - 5 V;
看起来合理吧?但是,想象一下,我以 2.5 V
的电压发送了一个数字。但是由于电路中的一些噪声,我在输出端得到 2.3 V
的电压,因此将其视为 0
。结果是?
有人给我发送了 1
,但我将其视为 0
。数据丢失可是一个非常严重的问题。
使用二进制则可靠得多,由于电压的高和低、电流的有和无等都是一种 质的变化,两种物理状态稳定、分明,因此,二进制码传输的抗干扰能力强,鉴别信息的可靠性高。
为什么计算机系统必须有时钟
建立数字系统的目的是 仅在某些时间点测试开/关(二进制)值,这使电线(或其他设备)有时间更换。这就是计算机系统有时钟的原因。
时钟会周期性地进行信号的测量,图中所示的 T1 和 T2 就是可以测量信号的时间点。
时钟利用所有这些时间点来保持同步。更快的时钟意味着每秒可以对电线进行更多次测试,并且整个系统运行得更快。2 GHz
处理器每秒检查二进制值 20
亿次。在这些时间之间,允许值改变并稳定下来。处理器芯片速度越快,每秒可以测试的次数就越多,每秒可以做出的决策就越多。
理由三:运算规则简单
数学推导已经证明,对 N
进制数进行算术求和或求积运算,其运算规则各有 N(N+1)/2
种。如采用十进制,则 N=10
,就有 55
种求和或求积的运算规则;而采用二进制,则 N=2
,仅有 3
种求和或求积的运算规则,以上面提到的加法为例:
0+0=0,0+1=1 (1+0=1),1+1=10
因而可以大大简化运算器等物理器件的设计。
理由四:逻辑判断方便
采用二进制后,仅有的两个符号 “1”
和 “0”
正好可以与逻辑命题的两个值 “真” 和 “假” 相对应,能够方便地使用逻辑代数这一有力工具来分析和设计计算机的逻辑电路。
虽然在 1950
年代就造出了更加高效的三元计算机,但在效率和复杂度的取舍上,始终抵不过二进制。二进制仍然在当今世界中长期存在。
CPU 的实际工作方式简单演示
上面我们了解到计算机以二进制的形式运行,它们只有两种状态:开(1)和关(0),为了执行二进制计算,我们需要采用一种特殊的电子元器件,称为 「晶体管」。暂时我们把它理解为一种开关吧,通电就打开,没电流通过就关闭。
利用「开关」搭建逻辑电路
我们知道,给电灯通上电,它就会亮:
于是,结合上开关,我们就能搭建出最基础的 与门 和 或门。
与门
该电路的逻辑是:只有当 A 和 B 同时开启时,LED 灯才会亮,也就是认为输出 1,我们可以利用电信号来简单模拟一下:
A | B | Y |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
或门
该电路的逻辑是:当 A 或者 B 开启时,LED 灯就会亮,也就是认为输出 1,我们可以利用电信号来简单模拟一下:
A | B | Y |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
其他门
类似地,我们可以借助更多的电子元器件来创造出基础的 7
种逻辑门电路:
这里需要特别提一下 异或门,我们需要先知道有一种电子元器件可以利用电气特性对 输入取反,也就是说输入 1
则输出 0
,输入 0
则输出 1
,那么我们就可以 简单模拟 出异或门逻辑电路(实际会更复杂些,这里仅展示出异或的意思):
A'
和 B'
分别表示 A
和 B
开关的反值,从图中我们很容易知道只有当 A
、B
只存在一个输入 1
时,整个电路才会输出 1
。
利用逻辑门制作简单加法器演示
OK,上面我们了解到我们能够利用 "开关" 来模拟逻辑的运算,我们接下来试着还原一个简单的加法运算器是如何实现的:
仅需两个门,就可以完成基本的二进制加法运算。上图是利用 logic.ly
创建的半加法器,A
、B
相当于使我们计算的两个数,最后一块相当于是我们的数显芯片,它的功能是根据输入显示数字,从上到下的引脚(也就是图中输入的地方,通常我们这样称呼)分别对应了 20=1、21=2、22=4、23=8 的输入,没有任何输入时显示为 0
,如果 引脚 1
(对应 20=1)像上图一样有输入,则显示 0 + 1 = 1
。
我们来理解一下上方的电路:
- 如果仅打开一个输入,但不同时打开两个输入,则此处的 XOR 门(异或门)将打开,此时对应输入
引脚 1
,显示数字 1
(类似于1 + 0 和 0 + 1
); - 如果两个输入均打开,则 AND 门(与门)将打开,此时对应输入
引脚 2
,显示数字 2
(类似于1 + 1
); - 如果没有输入,则 AND 门和 XOR 门都保持关闭,此时显示
数字 0
(类似于0 + 0
);
因此,如果两个都打开,则 XOR 保持关闭,并且 AND 门打开,得出正确的答案为 2
:
但这只是最基础的半加法运算器,不是太有用,因为它只能解决最简单的数学问题之一。但如果我们把它们两个与另一个输入连接,就会得到一个完整的加法器:
仔细思考几遍,你就会得知这个三个输入的加法器已经可以计算 3
个二进制数字的加法运算了,我们如法炮制,可以通过连接更多的"进位"来使这个加法器能够运算更多的数,这当然也意味着这个计算链条更长。
大多数其他数学运算都可以加法完成。乘法只是重复加法,减法可以通过一些奇特的位反转来完成,而除法只是重复减法。并且,尽管所有现代计算机都具有基于硬件的解决方案以加快更复杂的操作,但从技术上讲,您可以使用完整的加法器来完成全部操作。
一句话总结
编码伴随探索和学习新知的过程,如果愿意,我们能从中获取很多类似于解出一道数学题的乐趣。
Part 3. 机器指令到高级语言 | 站在先驱巨人的肩膀
机器指令
我们已经了解了 二进制和 CPU 的基本原理,知道了程序运行时,CPU 每秒数以亿次、十亿次、百亿次地震荡着时钟,同步执行着微小的 「电子操作」,例如:从内存读取一个字节的数据到 CPU 又或者判断字节中的某一位是 0
还是 1
。
CPU 本身有一组 规定好的 可以执行的 「基本动作」(被称为 机器指令):
- 读取指令;2. 执行指令;3. 写寄存器;
这几乎就是 CPU 工作的全部了。 这些动作虽然每次只能执行一次,但是每秒可以执行数十亿次,这个数量级的「小操作」累加成为一个大的「有用的操作」。
处理器所做的一切都是基于这些微小的操作!幸运的是,我们已经不再需要了解这些操作的详细信息就可以编写和使用各类程序。诸如 Java 这一类的 「高级语言」 的 目的 就是 将这些微小的电子操作组织成由人类可读的「程序语言」表示的大型有用单元。
尽管机器语言有一些反人类,但至少我们可以用它来来编写代码了,如:
00000001 00000010 00000001
00000100 00000100 00000000
似乎看着这一段儿代码有点迷糊,并且 可读性太差了。
如此你就会感知到 上个世纪 的程序员使用 打孔卡片:
使用 纸带:
甚至是 直接插拔线路 or 按下开关:
是一件多么硬核的事情...
汇编语言
PU 的指令都是 二进制 的,这显然对于人类来说是 不可读 的。为了解决二进制指令的可读性问题,工程师将那些指令写成了 八进制。二进制转八进制是轻而易举的,但是八进制的可读性也不行。
很自然地,最后还是用文字表达,加法指令写成 ADD
。内存地址也不再直接引用,而是 用标签 表示。
这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling
,完成这个步骤的程序就叫做 assembler
。它处理的文本,自然就叫做 aseembly code
。标准化以后,称为 assembly language
,缩写为 asm
,中文译为 汇编语言。
汇编语言演示
举个简单的例子,我们需要计算:
(1 + 4) * 2 + 3
我们按照 「后缀表示法」 进行一下转换:
1,4,+,2,*,3,+
我们平常使用的方法是 「中缀表示法」,也就是把计算符号放中间,例如 1 + 3
,后缀则是把符号放最后,例如 1, 3, +
。
这样做的好处是没有先乘除后加减的影响,也没有括号,直接运算就行了。(例如 1, 3, +
,先把 1
和 3
保存起来碰到 +
知道是加法则直接相加)
OK,我们从头开始使用汇编语言来编写一下程序,首先第一步:把 1
保存起来(放入寄存器):
MOV 1
之后是 4, +
,那就直接加一下:
ADD 4
然后是 2, *
,那就直接乘一下(SHL
是向左移动一位的意思,二进制中左移一个单位就相当于乘以 2
,例如 01
表示 1
,而 10
则表示 2
):
SHL 0
最后是 3, +
,再加一下:
ADD 3
完整程序如下:
MOV 1
ADD 4
SHL 0
ADD 3
这似乎看起来比 00001111
这样的二进制要好上太多了!程序员们感动到落泪:
高级语言
摆脱了 二进制,我们有了更可读的 汇编语言,但仍然十分繁琐和复杂,每一条汇编指令代表一个基本操作,例如:「从内存 x 位置获取一个数字并放入寄存器 A」、「将寄存器 A 中的数字添加到寄存器 B 的数字上」。这样的编程风格既费时又容易出错,并且一旦出错还很难发现。
例如,我们来看一看 「1969 年阿波罗 11号登月计划」 用来 防止登月舱计算机耗尽自身资源 的 BAILOUT 代码:
POODOO INHINT
CA Q
TS ALMCADR
TC BANKCALL
CADR VAC5STOR # STORE ERASABLES FOR DEBUGGING PURPOSES.
INDEX ALMCADR
CAF 0
ABORT2 TC BORTENT
OCT77770 OCT 77770 # DONT MOVE
CA V37FLBIT # IS AVERAGE G ON
MASK FLAGWRD7
CCS A
TC WHIMPER -1 # YES. DONT DO POODOO. DO BAILOUT.
TC DOWNFLAG
ADRES STATEFLG
TC DOWNFLAG
ADRES REINTFLG
TC DOWNFLAG
ADRES NODOFLAG
TC BANKCALL
CADR MR.KLEAN
TC WHIMPER
- 出处:改变世界的代码行 - https://www.infoq.cn/article/5CaYH8NbS6BmptWKRgkX
似乎不太容易读的样子...
阿波罗登月计划的源代码在 Github 上已经公开,有兴趣的可以去下方链接膜拜一下(可以去感受一下当时程序员的工程能力):
另外附一下当时代码的设计负责人 Margaret Heafield Hamilton(女程序员)和完成的堆起来跟人一样高的代码量:
第一个高级语言:FORTRAN
当 John Backus 在 1950
年以一名科学程序员的身份加入 IBM 时,已经可以使用诸如 ADD
之类的助记词代替数字代码来编写程序,也就是我们的汇编语言。这使编程变得容易一些,但是即使是一个简单的程序也需要数十次操作,并且仍然很难找到错误。
巴克斯认为,应该有可能创建一种编程语言,使一系列计算可以用类似于数学符号的形式来表达。然后,使用特定的翻译程序(以今天的术语来说是编译器)可以将其转换为计算机可以理解的数字代码。
Backus 在 1953
年向他的经理提出了这个想法。他得到了预算,并被鼓励雇用一个小团队来测试该想法的可行性。三年后,该团队发布了一本手册,其中描述了 IBM Mathematical Formula Translating System(简称 FORTRAN)。不久之后, IBM 向 IBM 704 的用户提供了第一个 FORTRAN 编译器。
Backus 和他的团队创造了世界上第一种高级编程语言。科学家和工程师将不再需要将其程序编写为数字代码或冗长的助记符。
FORTRAN 代码演示
下面演示计算并输出 8 * 6
的代码实例:
program VF0944
implicit none
integer a, b, c
a= 8
b= 6
c= a*b
print *, 'Hello World, a, b, c= ', a, b, c
end program VF0944
对比汇编代码,是不是看上去要清晰(人类可读)多了呢?
FORTRAN 的意义
FORTRAN 的问世在计算机史上具有划时代的意义,它使计算机语言从原始的低级汇编语言走出来,进入了更高的境界,使得 计算机语言不再是计算机专家的专利,使广大的工程技术人员有了进行计算机编程的手段。
由此计算机更快地深入到了社会之中,它在工业部门中初露头角,更是在火箭、导弹、人造地球卫星的设计中大显身手,因此有人称 FORTRAN 语言使计算机的工业应用成了可能,是推动第二次世界大战以后西方工业经济复苏和进入第二次工业革命的无形力量,是 "看不见的蒸汽机"。
一句话总结
计算机先驱们已经为我们架设好了计算机的世界,站在先驱巨人们的肩膀,我们如今很容易也可以看得更高和更远。
Part 4. 构建自己独有的程序(世界)
编码的乐趣有很多,把自己的想法动手实践就是其中一个。
例如网友玩儿游戏活动需要不停点鼠标,于是写了一个鼠标连点器:
例如给自己心爱的妹子写一个恋爱纪念网站:
例如给 FlappyBird 加一个强化学习算法让它自己学习如何飞行:(图示已经能自己飞 4527
步了)
例如网友自己写的文言文语言:(使用文言文写代码)
// HelloWorld 程序演示
吾有一數。曰三。名之曰「甲」。
為是「甲」遍。
吾有一言。曰「「問天地好在。」」。書之。
云云。
运行输出:
問天地好在。
問天地好在。
問天地好在。
一句话总结
我们可以动手通过编程把我们的很多想法付诸于实现 (前提是不断探索和学习),并在迎接挑战和最终实现的过程中获得无限的乐趣。
小总结
总体而言,IT 是令人兴奋的。
素有「软件吞噬世界」的说法,我们也正生活在计算机当道的世界。
并且编程并不是每个人都具备的技能,借助技术,一切皆有可能,并且现在互联网时代比以往都更有机会学习和创建「自己的世界」。
- 本文已收录至我的 Github 程序员成长系列 【More Than Java】,学习,不止 Code,欢迎 star:https://github.com/wmyskxz/MoreThanJava
- 个人公众号 :wmyskxz,个人独立域名博客:wmyskxz.com,坚持原创输出,下方扫码关注,2020,与您共同成长!
非常感谢各位人才能 看到这里,如果觉得本篇文章写得不错,觉得 「我没有三颗心脏」有点东西 的话,求点赞,求关注,求分享,求留言!
创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!