聪明的人类发现把简单的开关组合起来可以表达复杂的bool逻辑,在此基础之上构建了 CPU ,因此 CPU 只能简单的理解开关,用数字表达就是0和1。
创世纪:聪明的笨蛋
CPU 相当原始,就像单细胞生物一样,只能把数据从一个地方搬到另一个地方、简单的加一下,没有任何高难度动作,这些操作虽然看上去很简单很笨,但 CPU 有一个无与伦比的优势,那就是一个字:快,这是人类比不了了的,CPU 出现后人类开始拥有第二个大脑。就是这样原始的一个物种开始支配起另一个叫做程序员的物种。
干活的是大爷
一般来说两个不同的物种要想交流,比如人和鸟,就会有两种方式:要不就是鸟说人话,让人听懂;要不就是人说鸟语,让鸟听懂;就看谁厉害了。
最开始 CPU 胜出,程序员开始说鸟语并认真感受 CPU 的支配地位,好让 CPU 大爷可以工作,感受一下最开始的程序员是怎么说鸟语的:
程序员按照 CPU 的旨意直接用0和1编写指令,你没有看错,这破玩意就是代码了,就是这么原生态,然后放到打孔纸带上输入给CPU,CPU 开始工作,这时的程序可真的是看得见摸得着,就是有点浪费纸。
这时程序员必须站在 CPU 的角度来写代码,画风是这样的:
- 1101101010011010
- 1001001100101001
- 1100100011011110
- 1011101101010010
乍一看你知道这是什么意思吗?你不知道,心想:“这是什么破玩意?”,但 CPU 知道,心想“这就简直就是世界上最美的语言”。
天降大任
终于有一天程序员受够了说鸟语,好歹也是灵长类,叽叽喳喳说鸟语太没面子,你被委以重任:让程序员说人话。
你没有苦其心志劳其筋骨,而是仔细研究了一下 CPU,发现 CPU 执行的指令集来来回回就那么几个指令,比如加法指令、跳转指令等等,因此你把机器指令和对应的具体操作做了一个简单的映射,把机器指令映射到人类能看懂的单词,这样上面的01串就变成了:
- sub $8, %rsp
- mov $.LC0, %edi
- call puts
- mov $0, %eax
这样,程序员不必生硬的记住1011.....,而是记住人类可以认识的ADD SUB MUL DIV等这样的单词即可。
汇编语言就这样诞生了,编程语言中首次出现了人类可以认识的东西。
这时程序员终于不用再“叽叽喳喳。。”,而是升级为“阿巴阿巴。。”,虽然人类认知“阿巴阿巴”这几个字,但这和人类的语言在形式上差别还是有点大。
细节 VS 抽象
尽管汇编语言已经有人类可以认识的单词,但汇编语言和机器语言一样都属于低级语言。
所谓低级语言是说你需要关心所有细节。
关心什么细节呢?我们说过,CPU 是非常原始的东西,只知道把数据从一个地方搬到另一个地方,简单的操作一下再从一个地方搬到另一地方。
因此,如果你想用低级语言来编程的话,你需要使用多个“把数据从一个地方搬到另一个地方,简单的操作一下再从一个地方搬到另一地方”这样的简单指令来实现诸如排序这样复杂的问题。
有的同学可能对此感触不深,这就好比,本来你想表达“去给我端杯水过来”:
如果你用汇编这种低级语言就得这样实现:
我想你已经 Get 到了。
弥补差异
CPU 实在太简单了,简单到不能了理解任何稍微抽象一点诸如“给我端杯水”这样的东西,但人类天生习惯抽象化的表达,人类和机器的差距有办法来弥补吗?
换句话说就是有没有一种办法可以自动把人类抽象的表达转为 CPU 可以理解的具体实现,这显然可以极大增强程序员的生产力,现在,这个问题需要你来解决。
套路,都是套路
思来想去你都不知道该怎么把人类的抽象自动转为 CPU 能理解的具体实现,就在要放弃的时候你又看了一眼 CPU 可以理解的一堆细节:
电光火石之间灵光乍现,你发现了满满的套路,或者说模式。大部分情况下 CPU 执行的指令平铺直叙的,就像这样:
这些都是告诉 CPU 完成某个特定动作,你给这些平铺直叙的指令起了个名字,姑且就叫陈述句吧,statement。
除此之外,你还发现了这样的套路,那就是需要根据某种特定状态决定走哪段指令,这个套路在人看来就是“如果。。。就。。。否则。。就。。。”:
- if ***
- blablabla
- else ***
- blablabla
在某些情况下还需要不断重复一些指令,这个套路看起来就是原地打转:
- while ***
- blablabla
最后就是这里有很多看起来差不多的指令,就像这里:
这些指令是重复的,只是个别细节有所差异,把这些差异提取出来,剩下的指令打包到一起,用一个代号来指定这些指令就好了,这要有个名字,就叫函数吧:
- func abc:
- blablabla
现在你发现了所有套路:
- // 条件转移
- if ***
- blablabla
- else ***
- blablabla
- // 循环
- while ***
- blablabla
- // 函数
- func abc:
- blablabla
这些相比汇编语言已经有了质的飞跃,因为这已经和人类的语言非常接近了。接下来你发现自己面临两个问题:
- 这里的blablabla该是什么呢?
- 该怎样把上面的人类可以认识的字符串转换为 CPU 可以认识的机器指令
盗梦空间
你想起来了,上文说过大部分代码都是平铺直叙的陈述句,statement,这里的blablabla 仅仅就是一堆陈述句吗?
显然不是,blablabla 可以是陈述句,当然也可以是条件转移if else,也可以是循环while,也可以是调用函数,这样才合理。
虽然这样合理,很快你就发现了另一个严重的问题:
blabalbla中可以包含 if else 等语句,而if else等语句中又可以包含blablabla,blablabla中反过来又双可能会包含if else等语句,if else等语句又双叒有可能会包含blablabla,blablabla又双叒叕可能会包含if else等语句。。。
就像盗梦空间一样,一层梦中还有一层梦,梦中之梦,梦中之梦中之梦。。。一层嵌套一层,子子孙孙无穷匮也。。。
此时你已经明显感觉脑细胞不够用了,这也太复杂了吧,绝望开始吞噬你,上帝以及老天爷啊,谁来救救我!
此时你的高中老师过来拍了拍你的肩膀,递给了你一本高中数学课本,你恼羞成怒,给我这破玩意干什么,我现在想的问题这么高深,岂是一本破高中数学能解决的了的,抓过来一把扔在了地上。此时一阵妖风吹过,教材停留在了这样一页,上面有这样一个数列表达:
f(x) = f(x-1) + f(x-2)
这个递归公式在表达什么呢?f(x)的值依赖f(x-1),f(x-1)的值又依赖f(x-2),f(x-2)的值又依赖。。。
一层嵌套一层,梦中之梦,if中嵌套 statement,statement 又可以嵌套if。。。
等一下,这不就是递归嘛,上面看似无穷无尽的嵌套也可以用递归表达啊!你的数学老师仰天大笑,too young too simple,留下羞愧的你佛手而去,看似高科技的东西竟然用高中数学就解决了,一时震惊的目瞪狗带不知所措无地自容。有了递归这个概念加持,聪明的智商又开始占领高地了。
递归:代码的本质
不就是嵌套嘛,一层套一层嘛,递归天生就是来表达这玩意的 (提示:这里的表达并不完备,真实的编程语言不会这么简单):
- if : if bool statement else statement
- for: while bool statement
- statement: if | for | statement
上面一层嵌套一层的盗梦空间原来可以这么简洁的几句表达出来啊,你给这几句表达起了高端的名字,语法。数学,就是可以让一切都变得这么优雅。世界上所有的代码,不管有多么复杂最终都可以归结到语法上,原因也很简单,所有的代码都是按照语法的形式写出来的嘛。至此,你发明了真正的人类可以认识的编程语言。之前提到的第一个问题解决了,但仅仅有语言还是不够的。
让计算机理解递归
现在还差一个问题,怎样才能把这语言最终转化为 CPU 可以认识的机器指令呢?
人类可以按照语法写出代码,这些代码其实就是一串字符,怎么让计算机也能认识用递归语法表达的一串字符呢?
这是一项事关人类命运的事情,你不禁感到责任重大,但这最后一步又看似困难重重,你不禁仰天长叹,计算机可太难了。
此时你的初中老师过来拍了拍你的肩膀,递给了你一本初中植物学课本,你恼羞成怒,给我这破玩意干什么,我现在想的问题这么高深,岂是一本破初中教科书能解决的了的,抓过来一把扔在了地上。
此时又一阵妖风挂过,书被翻到了介绍树的一章,你望着这一页不禁发起呆来:
树干下面是树枝,树枝下是树叶,树枝下也可以是树枝,树枝下还可以是树枝、吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮,哎?这句不对,回到上面这句,树干生树枝,树枝还可以生树枝,一层套一层、梦中之梦、子子孙孙无穷匮、高中数学老师,等一下,这也是递归啊!!!我们可以把根据递归语法写出来的的代码用树来表示啊!
你的初中老师仰天大笑,图样图森破,看似高科技的东西竟然靠初中知识就解决了。
优秀的翻译官
计算机处理编程语言时可以按照递归定义把代码用树的形式组织起来,由于这棵树是按照语法生成的,姑且就叫语法树吧。
现在代码被表示成了树的形式,你仔细观察后发现,其实叶子节点的表达是非常简单的,可以很简单的翻译成对应的机器指令,只要叶子节点翻译成了机器指令,你就可以把此结果应用到叶子节点的父节点,父节点又可以把翻译结果引用到父节点的父节点,一层层向上传递,最终整颗树都可以翻译成具体的机器指令。
完成这个工作的程序也要有个名字,根据“弄不懂原则”,你给这个类似翻译的程序起了个不怎么响亮的名字,编译器,compiler。
现在你还觉得二叉树之类的数据结构没啥用吗?至此,你完成了一项了不起的发明创造,程序员可以用人类认识的东西来写代码,你编写的一个叫做编译器的程序负责将其翻译成 CPU 可以认识的机器指令。后人根据你的思想构建出了C/C++、以及后续的Java、Python,这些语言现在还有一帮人在用呢。
总结
世界上所有的编程语言都是遵照特定语法来编写的,编译器根据该语言的语法将代码解析成语法树,遍历语法树生成机器指令(C/C++)或者字节码等(Java),然后交给 CPU(或者虚拟机)来执行。
也因此,高级语言的抽象表达能力很强,代价都是牺牲了对底层的控制能力,这就是为什么操作系统的一部分需要使用汇编语言编写,汇编语言对底层细节的强大控制力是高级语言替代不了的。
最后请注意,本文为通俗易懂讲解编程语言牺牲了严谨性,这里的语法没有体现函数、表达式等等,真实语言的语法远远比这里的复杂,此外关于编译器也不会直接把语法树翻译成机器语言,而是生成一种类似机器指令的中间语言,经过一系列复杂的优化后最终生成真正的机器指令,真实的编译器远比这里复杂。
希望本文对大家理解编程语言有所帮助。