天天看點

假如你來發明程式設計語言

聰明的人類發現把簡單的開關組合起來可以表達複雜的bool邏輯,在此基礎之上建構了 CPU ,是以 CPU 隻能簡單的了解開關,用數字表達就是0和1。

假如你來發明程式設計語言

創世紀:聰明的笨蛋

CPU 相當原始,就像單細胞生物一樣,隻能把資料從一個地方搬到另一個地方、簡單的加一下,沒有任何高難度動作,這些操作雖然看上去很簡單很笨,但 CPU 有一個無與倫比的優勢,那就是一個字:快,這是人類比不了了的,CPU 出現後人類開始擁有第二個大腦。

就是這樣原始的一個物種開始支配起另一個叫做程式員的物種。

幹活的是大爺

一般來說兩個不同的物種要想交流,比如人和鳥,就會有兩種方式:要不就是鳥說人話,讓人聽懂;要不就是人說鳥語,讓鳥聽懂;就看誰厲害了。

最開始 CPU 勝出,程式員開始說鳥語并認真感受 CPU 的支配地位,好讓 CPU 大爺可以工作,感受一下最開始的程式員是怎麼說鳥語的:

假如你來發明程式設計語言

程式員按照 CPU 的旨意直接用0和1編寫指令,你沒有看錯,這破玩意就是代碼了,就是這麼原生态,然後放到打孔紙帶上輸入給CPU,CPU 開始工作,這時的程式可真的是看得見摸得着,就是有點浪費紙。

這時程式員必須站在 CPU 的角度來寫代碼,畫風是這樣的:

乍一看你知道這是什麼意思嗎?你不知道,心想:“這是什麼破玩意?”,但 CPU 知道,心想“這就簡直就是世界上最美的語言”。

天降大任

終于有一天程式員受夠了說鳥語,好歹也是靈長類,叽叽喳喳說鳥語太沒面子,你被委以重任:讓程式員說人話。

你沒有苦其心志勞其筋骨,而是仔細研究了一下 CPU,發現 CPU 執行的指令集來來回回就那麼幾個指令,比如加法指令、跳轉指令等等,是以你把機器指令和對應的具體操作做了一個簡單的映射,把機器指令映射到人類能看懂的單詞,這樣上面的01串就變成了:

這樣,程式員不必生硬的記住1011.....,而是記住人類可以認識的ADD SUB MUL DIV等這樣的單詞即可。

假如你來發明程式設計語言

彙編語言就這樣誕生了,程式設計語言中首次出現了人類可以認識的東西。

這時程式員終于不用再“叽叽喳喳。。”,而是更新為“阿巴阿巴。。”,雖然人類認知“阿巴阿巴”這幾個字,但這和人類的語言在形式上差别還是有點大。

細節 VS 抽象

盡管彙編語言已經有人類可以認識的單詞,但彙編語言和機器語言一樣都屬于低級語言。

所謂低級語言是說你需要關心所有細節。

關心什麼細節呢?我們說過,CPU 是非常原始的東西,隻知道把資料從一個地方搬到另一個地方,簡單的操作一下再從一個地方搬到另一地方。

是以,如果你想用低級語言來程式設計的話,你需要使用多個“把資料從一個地方搬到另一個地方,簡單的操作一下再從一個地方搬到另一地方”這樣的簡單指令來實作諸如排序這樣複雜的問題。

有的同學可能對此感觸不深,這就好比,本來你想表達“去給我端杯水過來”:

假如你來發明程式設計語言

如果你用彙編這種低級語言就得這樣實作:

假如你來發明程式設計語言

我想你已經 Get 到了。

彌補差異

CPU 實在太簡單了,簡單到不能了了解任何稍微抽象一點諸如“給我端杯水”這樣的東西,但人類天生習慣抽象化的表達,人類和機器的差距有辦法來彌補嗎?

換句話說就是有沒有一種辦法可以自動把人類抽象的表達轉為 CPU 可以了解的具體實作,這顯然可以極大增強程式員的生産力,現在,這個問題需要你來解決。

假如你來發明程式設計語言

套路,都是套路

思來想去你都不知道該怎麼把人類的抽象自動轉為 CPU 能了解的具體實作,就在要放棄的時候你又看了一眼 CPU 可以了解的一堆細節:

假如你來發明程式設計語言

電光火石之間靈光乍現,你發現了滿滿的套路,或者說模式。

大部分情況下 CPU 執行的指令平鋪直叙的,就像這樣:

假如你來發明程式設計語言

這些都是告訴 CPU 完成某個特定動作,你給這些平鋪直叙的指令起了個名字,姑且就叫陳述句吧,statement。

除此之外,你還發現了這樣的套路,那就是需要根據某種特定狀态決定走哪段指令,這個套路在人看來就是“如果。。。就。。。否則。。就。。。”:

在某些情況下還需要不斷重複一些指令,這個套路看起來就是原地打轉:

最後就是這裡有很多看起來差不多的指令,就像這裡:

假如你來發明程式設計語言

這些指令是重複的,隻是個别細節有所差異,把這些差異提取出來,剩下的指令打包到一起,用一個代号來指定這些指令就好了,這要有個名字,就叫函數吧:

現在你發現了所有套路:

這些相比彙編語言已經有了質的飛躍,因為這已經和人類的語言非常接近了。

接下來你發現自己面臨兩個問題:

這裡的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-1)的值又依賴f(x-2),f(x-2)的值又依賴。。。

假如你來發明程式設計語言

一層嵌套一層,夢中之夢,if中嵌套 statement,statement 又可以嵌套if。。。

等一下,這不就是遞歸嘛,上面看似無窮無盡的嵌套也可以用遞歸表達啊!

你的數學老師仰天大笑,too young too simple,留下羞愧的你佛手而去,看似高科技的東西竟然用高中數學就解決了,一時震驚的目瞪狗帶不知所措無地自容。

有了遞歸這個概念加持,聰明的智商又開始占領高地了。

遞歸:代碼的本質

不就是嵌套嘛,一層套一層嘛,遞歸天生就是來表達這玩意的 (提示:這裡的表達并不完備,真實的程式設計語言不會這麼簡單):

上面一層嵌套一層的盜夢空間原來可以這麼簡潔的幾句表達出來啊,你給這幾句表達起了高端的名字,文法。

數學,就是可以讓一切都變得這麼優雅。

世界上所有的代碼,不管有多麼複雜最終都可以歸結到文法上,原因也很簡單,所有的代碼都是按照文法的形式寫出來的嘛。

至此,你發明了真正的人類可以認識的程式設計語言。

之前提到的第一個問題解決了,但僅僅有語言還是不夠的。

讓計算機了解遞歸

現在還差一個問題,怎樣才能把這語言最終轉化為 CPU 可以認識的機器指令呢?

人類可以按照文法寫出代碼,這些代碼其實就是一串字元,怎麼讓計算機也能認識用遞歸文法表達的一串字元呢?

這是一項事關人類命運的事情,你不禁感到責任重大,但這最後一步又看似困難重重,你不禁仰天長歎,計算機可太難了。

此時你的國中老師過來拍了拍你的肩膀,遞給了你一本國中植物學課本,你惱羞成怒,給我這破玩意幹什麼,我現在想的問題這麼高深,豈是一本破國中教科書能解決的了的,抓過來一把扔在了地上。

此時又一陣妖風挂過,書被翻到了介紹樹的一章,你望着這一頁不禁發起呆來:

假如你來發明程式設計語言

樹幹下面是樹枝,樹枝下是樹葉,樹枝下也可以是樹枝,樹枝下還可以是樹枝、吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮,哎?這句不對,回到上面這句,樹幹生樹枝,樹枝還可以生樹枝,一層套一層、夢中之夢、子子孫孫無窮匮、高中數學老師,等一下,這也是遞歸啊!!!我們可以把根據遞歸文法寫出來的的代碼用樹來表示啊!

假如你來發明程式設計語言

你的國中老師仰天大笑,圖樣圖森破,看似高科技的東西竟然靠國中知識就解決了。

優秀的翻譯官

計算機處理程式設計語言時可以按照遞歸定義把代碼用樹的形式組織起來,由于這棵樹是按照文法生成的,姑且就叫文法樹吧。

現在代碼被表示成了樹的形式,你仔細觀察後發現,其實葉子節點的表達是非常簡單的,可以很簡單的翻譯成對應的機器指令,隻要葉子節點翻譯成了機器指令,你就可以把此結果應用到葉子節點的父節點,父節點又可以把翻譯結果引用到父節點的父節點,一層層向上傳遞,最終整顆樹都可以翻譯成具體的機器指令。

假如你來發明程式設計語言

完成這個工作的程式也要有個名字,根據“弄不懂原則”,你給這個類似翻譯的程式起了個不怎麼響亮的名字,編譯器,compiler。

現在你還覺得二叉樹之類的資料結構沒啥用嗎?

至此,你完成了一項了不起的發明創造,程式員可以用人類認識的東西來寫代碼,你編寫的一個叫做編譯器的程式負責将其翻譯成 CPU 可以認識的機器指令。

後人根據你的思想建構出了C/C++、以及後續的Java、Python,這些語言現在還有一幫人在用呢。

總結

世界上所有的程式設計語言都是遵照特定文法來編寫的,編譯器根據該語言的文法将代碼解析成文法樹,周遊文法樹生成機器指令(C/C++)或者位元組碼等(Java),然後交給 CPU(或者虛拟機)來執行。

也是以,進階語言的抽象表達能力很強,代價都是犧牲了對底層的控制能力,這就是為什麼作業系統的一部分需要使用彙編語言編寫,彙編語言對底層細節的強大控制力是進階語言替代不了的。

最後請注意,本文為通俗易懂講解程式設計語言犧牲了嚴謹性,這裡的文法沒有展現函數、表達式等等,真實語言的文法遠遠比這裡的複雜,此外關于編譯器也不會直接把文法樹翻譯成機器語言,而是生成一種類似機器指令的中間語言,經過一系列複雜的優化後最終生成真正的機器指令,真實的編譯器遠比這裡複雜。

希望本文對大家了解程式設計語言有所幫助。

什麼是程式?

編譯器是如何工作的?

程式員應如何了解抽象?

有道無術,術可成;有術無道,止于術

假如你來發明程式設計語言