假如你來(lái)發(fā)明編程語(yǔ)言
聰明的人類(lèi)發(fā)現(xiàn)把簡(jiǎn)單的開(kāi)關(guān)組合起來(lái)可以表達(dá)復(fù)雜的bool邏輯,在此基礎(chǔ)之上構(gòu)建了 CPU ,因此 CPU 只能簡(jiǎn)單的理解開(kāi)關(guān),用數(shù)字表達(dá)就是0和1。
創(chuàng)世紀(jì):聰明的笨蛋
CPU 相當(dāng)原始,就像單細(xì)胞生物一樣,只能把數(shù)據(jù)從一個(gè)地方搬到另一個(gè)地方、簡(jiǎn)單的加一下,沒(méi)有任何高難度動(dòng)作,這些操作雖然看上去很簡(jiǎn)單很笨,但 CPU 有一個(gè)無(wú)與倫比的優(yōu)勢(shì),那就是一個(gè)字:快,這是人類(lèi)比不了了的,CPU 出現(xiàn)后人類(lèi)開(kāi)始擁有第二個(gè)大腦。
就是這樣原始的一個(gè)物種開(kāi)始支配起另一個(gè)叫做程序員的物種。
干活的是大爺
一般來(lái)說(shuō)兩個(gè)不同的物種要想交流,比如人和鳥(niǎo),就會(huì)有兩種方式:要不就是鳥(niǎo)說(shuō)人話,讓人聽(tīng)懂;要不就是人說(shuō)鳥(niǎo)語(yǔ),讓鳥(niǎo)聽(tīng)懂;就看誰(shuí)厲害了。
最開(kāi)始 CPU 勝出,程序員開(kāi)始說(shuō)鳥(niǎo)語(yǔ)并認(rèn)真感受 CPU 的支配地位,好讓 CPU 大爺可以工作,感受一下最開(kāi)始的程序員是怎么說(shuō)鳥(niǎo)語(yǔ)的:
程序員按照 CPU 的旨意直接用0和1編寫(xiě)指令,你沒(méi)有看錯(cuò),這破玩意就是代碼了,就是這么原生態(tài),然后放到打孔紙帶上輸入給CPU,CPU 開(kāi)始工作,這時(shí)的程序可真的是看得見(jiàn)摸得著,就是有點(diǎn)浪費(fèi)紙。
這時(shí)程序員必須站在 CPU 的角度來(lái)寫(xiě)代碼,畫(huà)風(fēng)是這樣的:
- 1101101010011010
- 1001001100101001
- 1100100011011110
- 1011101101010010
乍一看你知道這是什么意思嗎?你不知道,心想:“這是什么破玩意?”,但 CPU 知道,心想“這就簡(jiǎn)直就是世界上最美的語(yǔ)言”。
天降大任
終于有一天程序員受夠了說(shuō)鳥(niǎo)語(yǔ),好歹也是靈長(zhǎng)類(lèi),嘰嘰喳喳說(shuō)鳥(niǎo)語(yǔ)太沒(méi)面子,你被委以重任:讓程序員說(shuō)人話。
你沒(méi)有苦其心志勞其筋骨,而是仔細(xì)研究了一下 CPU,發(fā)現(xiàn) CPU 執(zhí)行的指令集來(lái)來(lái)回回就那么幾個(gè)指令,比如加法指令、跳轉(zhuǎn)指令等等,因此你把機(jī)器指令和對(duì)應(yīng)的具體操作做了一個(gè)簡(jiǎn)單的映射,把機(jī)器指令映射到人類(lèi)能看懂的單詞,這樣上面的01串就變成了:
- sub $8, %rsp
- mov $.LC0, %edi
- call puts
- mov $0, %eax
這樣,程序員不必生硬的記住1011.....,而是記住人類(lèi)可以認(rèn)識(shí)的ADD SUB MUL DIV等這樣的單詞即可。
匯編語(yǔ)言就這樣誕生了,編程語(yǔ)言中首次出現(xiàn)了人類(lèi)可以認(rèn)識(shí)的東西。
這時(shí)程序員終于不用再“嘰嘰喳喳。。”,而是升級(jí)為“阿巴阿巴。。”,雖然人類(lèi)認(rèn)知“阿巴阿巴”這幾個(gè)字,但這和人類(lèi)的語(yǔ)言在形式上差別還是有點(diǎn)大。
細(xì)節(jié) VS 抽象
盡管匯編語(yǔ)言已經(jīng)有人類(lèi)可以認(rèn)識(shí)的單詞,但匯編語(yǔ)言和機(jī)器語(yǔ)言一樣都屬于低級(jí)語(yǔ)言。
所謂低級(jí)語(yǔ)言是說(shuō)你需要關(guān)心所有細(xì)節(jié)。
關(guān)心什么細(xì)節(jié)呢?我們說(shuō)過(guò),CPU 是非常原始的東西,只知道把數(shù)據(jù)從一個(gè)地方搬到另一個(gè)地方,簡(jiǎn)單的操作一下再?gòu)囊粋€(gè)地方搬到另一地方。
因此,如果你想用低級(jí)語(yǔ)言來(lái)編程的話,你需要使用多個(gè)“把數(shù)據(jù)從一個(gè)地方搬到另一個(gè)地方,簡(jiǎn)單的操作一下再?gòu)囊粋€(gè)地方搬到另一地方”這樣的簡(jiǎn)單指令來(lái)實(shí)現(xiàn)諸如排序這樣復(fù)雜的問(wèn)題。
有的同學(xué)可能對(duì)此感觸不深,這就好比,本來(lái)你想表達(dá)“去給我端杯水過(guò)來(lái)”:
如果你用匯編這種低級(jí)語(yǔ)言就得這樣實(shí)現(xiàn):
我想你已經(jīng) Get 到了。
彌補(bǔ)差異
CPU 實(shí)在太簡(jiǎn)單了,簡(jiǎn)單到不能了理解任何稍微抽象一點(diǎn)諸如“給我端杯水”這樣的東西,但人類(lèi)天生習(xí)慣抽象化的表達(dá),人類(lèi)和機(jī)器的差距有辦法來(lái)彌補(bǔ)嗎?
換句話說(shuō)就是有沒(méi)有一種辦法可以自動(dòng)把人類(lèi)抽象的表達(dá)轉(zhuǎn)為 CPU 可以理解的具體實(shí)現(xiàn),這顯然可以極大增強(qiáng)程序員的生產(chǎn)力,現(xiàn)在,這個(gè)問(wèn)題需要你來(lái)解決。
套路,都是套路
思來(lái)想去你都不知道該怎么把人類(lèi)的抽象自動(dòng)轉(zhuǎn)為 CPU 能理解的具體實(shí)現(xiàn),就在要放棄的時(shí)候你又看了一眼 CPU 可以理解的一堆細(xì)節(jié):
電光火石之間靈光乍現(xiàn),你發(fā)現(xiàn)了滿(mǎn)滿(mǎn)的套路,或者說(shuō)模式。
大部分情況下 CPU 執(zhí)行的指令平鋪直敘的,就像這樣:
這些都是告訴 CPU 完成某個(gè)特定動(dòng)作,你給這些平鋪直敘的指令起了個(gè)名字,姑且就叫陳述句吧,statement。
除此之外,你還發(fā)現(xiàn)了這樣的套路,那就是需要根據(jù)某種特定狀態(tài)決定走哪段指令,這個(gè)套路在人看來(lái)就是“如果。。。就。。。否則。。就。。。”:
- if ***
- blablabla
- else ***
- blablabla
在某些情況下還需要不斷重復(fù)一些指令,這個(gè)套路看起來(lái)就是原地打轉(zhuǎn):
- while ***
- blablabla
最后就是這里有很多看起來(lái)差不多的指令,就像這里:
這些指令是重復(fù)的,只是個(gè)別細(xì)節(jié)有所差異,把這些差異提取出來(lái),剩下的指令打包到一起,用一個(gè)代號(hào)來(lái)指定這些指令就好了,這要有個(gè)名字,就叫函數(shù)吧:
- func abc:
- blablabla
現(xiàn)在你發(fā)現(xiàn)了所有套路:
- // 條件轉(zhuǎn)移
- if ***
- blablabla
- else ***
- blablabla
- // 循環(huán)
- while ***
- blablabla
- // 函數(shù)
- func abc:
- blablabla
這些相比匯編語(yǔ)言已經(jīng)有了質(zhì)的飛躍,因?yàn)檫@已經(jīng)和人類(lèi)的語(yǔ)言非常接近了。
接下來(lái)你發(fā)現(xiàn)自己面臨兩個(gè)問(wèn)題:
這里的blablabla該是什么呢?
該怎樣把上面的人類(lèi)可以認(rèn)識(shí)的字符串轉(zhuǎn)換為 CPU 可以認(rèn)識(shí)的機(jī)器指令
盜夢(mèng)空間
你想起來(lái)了,上文說(shuō)過(guò)大部分代碼都是平鋪直敘的陳述句,statement,這里的blablabla 僅僅就是一堆陳述句嗎?
顯然不是,blablabla 可以是陳述句,當(dāng)然也可以是條件轉(zhuǎn)移if else,也可以是循環(huán)while,也可以是調(diào)用函數(shù),這樣才合理。
雖然這樣合理,很快你就發(fā)現(xiàn)了另一個(gè)嚴(yán)重的問(wèn)題:
blabalbla中可以包含 if else 等語(yǔ)句,而if else等語(yǔ)句中又可以包含blablabla,blablabla中反過(guò)來(lái)又雙可能會(huì)包含if else等語(yǔ)句,if else等語(yǔ)句又雙叒有可能會(huì)包含blablabla,blablabla又雙叒叕可能會(huì)包含if else等語(yǔ)句。。。
就像盜夢(mèng)空間一樣,一層夢(mèng)中還有一層夢(mèng),夢(mèng)中之夢(mèng),夢(mèng)中之夢(mèng)中之夢(mèng)。。。一層嵌套一層,子子孫孫無(wú)窮匱也。。。
此時(shí)你已經(jīng)明顯感覺(jué)腦細(xì)胞不夠用了,這也太復(fù)雜了吧,絕望開(kāi)始吞噬你,上帝以及老天爺啊,誰(shuí)來(lái)救救我!
此時(shí)你的高中老師過(guò)來(lái)拍了拍你的肩膀,遞給了你一本高中數(shù)學(xué)課本,你惱羞成怒,給我這破玩意干什么,我現(xiàn)在想的問(wèn)題這么高深,豈是一本破高中數(shù)學(xué)能解決的了的,抓過(guò)來(lái)一把扔在了地上。
此時(shí)一陣妖風(fēng)吹過(guò),教材停留在了這樣一頁(yè),上面有這樣一個(gè)數(shù)列表達(dá):
- f(x) = f(x-1) + f(x-2)
這個(gè)遞歸公式在表達(dá)什么呢?f(x)的值依賴(lài)f(x-1),f(x-1)的值又依賴(lài)f(x-2),f(x-2)的值又依賴(lài)。。。
一層嵌套一層,夢(mèng)中之夢(mèng),if中嵌套 statement,statement 又可以嵌套if。。。
等一下,這不就是遞歸嘛,上面看似無(wú)窮無(wú)盡的嵌套也可以用遞歸表達(dá)啊!
你的數(shù)學(xué)老師仰天大笑,too young too simple,留下羞愧的你佛手而去,看似高科技的東西竟然用高中數(shù)學(xué)就解決了,一時(shí)震驚的目瞪狗帶不知所措無(wú)地自容。
有了遞歸這個(gè)概念加持,聰明的智商又開(kāi)始占領(lǐng)高地了。
遞歸:代碼的本質(zhì)
不就是嵌套嘛,一層套一層嘛,遞歸天生就是來(lái)表達(dá)這玩意的 (提示:這里的表達(dá)并不完備,真實(shí)的編程語(yǔ)言不會(huì)這么簡(jiǎn)單):
- if : if bool statement else statement
- for: while bool statement
- statement: if | for | statement
上面一層嵌套一層的盜夢(mèng)空間原來(lái)可以這么簡(jiǎn)潔的幾句表達(dá)出來(lái)啊,你給這幾句表達(dá)起了高端的名字,語(yǔ)法。
數(shù)學(xué),就是可以讓一切都變得這么優(yōu)雅。
世界上所有的代碼,不管有多么復(fù)雜最終都可以歸結(jié)到語(yǔ)法上,原因也很簡(jiǎn)單,所有的代碼都是按照語(yǔ)法的形式寫(xiě)出來(lái)的嘛。
至此,你發(fā)明了真正的人類(lèi)可以認(rèn)識(shí)的編程語(yǔ)言。
之前提到的第一個(gè)問(wèn)題解決了,但僅僅有語(yǔ)言還是不夠的。
讓計(jì)算機(jī)理解遞歸
現(xiàn)在還差一個(gè)問(wèn)題,怎樣才能把這語(yǔ)言最終轉(zhuǎn)化為 CPU 可以認(rèn)識(shí)的機(jī)器指令呢?
人類(lèi)可以按照語(yǔ)法寫(xiě)出代碼,這些代碼其實(shí)就是一串字符,怎么讓計(jì)算機(jī)也能認(rèn)識(shí)用遞歸語(yǔ)法表達(dá)的一串字符呢?
這是一項(xiàng)事關(guān)人類(lèi)命運(yùn)的事情,你不禁感到責(zé)任重大,但這最后一步又看似困難重重,你不禁仰天長(zhǎng)嘆,計(jì)算機(jī)可太難了。
此時(shí)你的初中老師過(guò)來(lái)拍了拍你的肩膀,遞給了你一本初中植物學(xué)課本,你惱羞成怒,給我這破玩意干什么,我現(xiàn)在想的問(wèn)題這么高深,豈是一本破初中教科書(shū)能解決的了的,抓過(guò)來(lái)一把扔在了地上。
此時(shí)又一陣妖風(fēng)掛過(guò),書(shū)被翻到了介紹樹(shù)的一章,你望著這一頁(yè)不禁發(fā)起呆來(lái):
樹(shù)干下面是樹(shù)枝,樹(shù)枝下是樹(shù)葉,樹(shù)枝下也可以是樹(shù)枝,樹(shù)枝下還可以是樹(shù)枝、吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮,哎?這句不對(duì),回到上面這句,樹(shù)干生樹(shù)枝,樹(shù)枝還可以生樹(shù)枝,一層套一層、夢(mèng)中之夢(mèng)、子子孫孫無(wú)窮匱、高中數(shù)學(xué)老師,等一下,這也是遞歸啊!!!我們可以把根據(jù)遞歸語(yǔ)法寫(xiě)出來(lái)的的代碼用樹(shù)來(lái)表示啊!
你的初中老師仰天大笑,圖樣圖森破,看似高科技的東西竟然靠初中知識(shí)就解決了。
優(yōu)秀的翻譯官
計(jì)算機(jī)處理編程語(yǔ)言時(shí)可以按照遞歸定義把代碼用樹(shù)的形式組織起來(lái),由于這棵樹(shù)是按照語(yǔ)法生成的,姑且就叫語(yǔ)法樹(shù)吧。
現(xiàn)在代碼被表示成了樹(shù)的形式,你仔細(xì)觀察后發(fā)現(xiàn),其實(shí)葉子節(jié)點(diǎn)的表達(dá)是非常簡(jiǎn)單的,可以很簡(jiǎn)單的翻譯成對(duì)應(yīng)的機(jī)器指令,只要葉子節(jié)點(diǎn)翻譯成了機(jī)器指令,你就可以把此結(jié)果應(yīng)用到葉子節(jié)點(diǎn)的父節(jié)點(diǎn),父節(jié)點(diǎn)又可以把翻譯結(jié)果引用到父節(jié)點(diǎn)的父節(jié)點(diǎn),一層層向上傳遞,最終整顆樹(shù)都可以翻譯成具體的機(jī)器指令。
完成這個(gè)工作的程序也要有個(gè)名字,根據(jù)“弄不懂原則”,你給這個(gè)類(lèi)似翻譯的程序起了個(gè)不怎么響亮的名字,編譯器,compiler。
現(xiàn)在你還覺(jué)得二叉樹(shù)之類(lèi)的數(shù)據(jù)結(jié)構(gòu)沒(méi)啥用嗎?
至此,你完成了一項(xiàng)了不起的發(fā)明創(chuàng)造,程序員可以用人類(lèi)認(rèn)識(shí)的東西來(lái)寫(xiě)代碼,你編寫(xiě)的一個(gè)叫做編譯器的程序負(fù)責(zé)將其翻譯成 CPU 可以認(rèn)識(shí)的機(jī)器指令。
后人根據(jù)你的思想構(gòu)建出了C/C++、以及后續(xù)的Java、Python,這些語(yǔ)言現(xiàn)在還有一幫人在用呢。
總結(jié)
世界上所有的編程語(yǔ)言都是遵照特定語(yǔ)法來(lái)編寫(xiě)的,編譯器根據(jù)該語(yǔ)言的語(yǔ)法將代碼解析成語(yǔ)法樹(shù),遍歷語(yǔ)法樹(shù)生成機(jī)器指令(C/C++)或者字節(jié)碼等(Java),然后交給 CPU(或者虛擬機(jī))來(lái)執(zhí)行。
也因此,高級(jí)語(yǔ)言的抽象表達(dá)能力很強(qiáng),代價(jià)都是犧牲了對(duì)底層的控制能力,這就是為什么操作系統(tǒng)的一部分需要使用匯編語(yǔ)言編寫(xiě),匯編語(yǔ)言對(duì)底層細(xì)節(jié)的強(qiáng)大控制力是高級(jí)語(yǔ)言替代不了的。
最后請(qǐng)注意,本文為通俗易懂講解編程語(yǔ)言犧牲了嚴(yán)謹(jǐn)性,這里的語(yǔ)法沒(méi)有體現(xiàn)函數(shù)、表達(dá)式等等,真實(shí)語(yǔ)言的語(yǔ)法遠(yuǎn)遠(yuǎn)比這里的復(fù)雜,此外關(guān)于編譯器也不會(huì)直接把語(yǔ)法樹(shù)翻譯成機(jī)器語(yǔ)言,而是生成一種類(lèi)似機(jī)器指令的中間語(yǔ)言,經(jīng)過(guò)一系列復(fù)雜的優(yōu)化后最終生成真正的機(jī)器指令,真實(shí)的編譯器遠(yuǎn)比這里復(fù)雜。
希望本文對(duì)大家理解編程語(yǔ)言有所幫助。
本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)的荒島求生」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)的荒島求生公眾號(hào)。