計算機語言是人與計算機進行交流的工具,是用來書寫計算機程序的工具。
可以通俗地理解為,你用用特定的語言與特定的對象(特定操作系統與CPU的計算機)溝通,關鍵是需要有個翻譯,這個翻譯就是編譯器或解釋器,同樣的語言,針對不同的對象(特定的CPU和操作系統)需要有不同的編譯器或解釋器。所以說編程語言是“設計”出來的,設計只需要思考和寫文檔,而該語言的編譯器或解釋器才是“開發”出來的。
(編譯原理講到了“自舉編譯器”。大意就是先用底層語言(應該是匯編)寫一個能運行,但效率極低的C語言編譯器(底層語言不好優化),有了C語言的編譯器以后,就可以用C語言好好寫一個編譯器了,用之前那個運行沒問題,但效率低得編譯器編譯一下,就得到了可以使用的編譯器了。)
編譯器也是程序,所以也需要用編程語言來編寫,很多編程語言是用別的更基礎的語言開發的,其中用最多的就是C語言。C語言編譯器很多,大部分都是用別的C語言編譯器編譯出來的,而最早的C語言編譯器是用匯編語言寫出來的,最早的匯編語言編譯器是通過“編譯器自舉”開發出來的。
從最基本的角度看,一種編程語言就是把一組特定的詞匯,按照一組特定的語法規則組合到一起,形成計算機可以通過某種方式“理解”的東西,可以讓計算機據此執行特定的動作。
首先要決定你想設計的語言應該解決什么問題。面對不同的領域、不同的需求、不同的抽象層級、不同的思考范式,也就產生了各有特長的編程語言。專注于高效、便捷地解決某特定范疇之內問題的語言,叫做領域專用語言(Domain Specific Language,DSL),而可以跨越若干領域解決問題的語言,叫做泛用語言(General-Purpose Language,GPL)。常見的 DSL 比如 MATLAB、SQL 等等;常見的 GPL 如匯編、C、Python。當然,兩類語言之間的分界并不是很明顯,有些語言一開始是作為 DSL 設計的,后來漸漸朝著 GPL 的方向發展,比如 PHP 和 JavaScript;反過來也有大量基于 GPL 開發而來的 DSL。
先看看這件事情的最底層。所謂“計算機執行動作”,其實只是“把一個二進制數字傳入 CPU,然后等待什么事情發生”的形而上描述。二進制計算機所能理解的唯一東西就是二進制數字,稱為“機器碼”。比如:
10110 000 01100001
這串數字,對于某顆 CPU 來說,就是“把 01100001 放到 000 號寄存器里”的指令,其中“10110”的部分,就是 CPU 能懂得的“放入”指令。這樣的指還有許許多多,比如做加法、求邏輯“與”,跳轉,加密等等,全都只是一些二進制數字而已。
對人類來說,這種純數字的寫法太難記憶,就把它轉寫成:
MOV AL, 97
其中 MOV 代表“10110”,AL 代表 000 號寄存器,97 則是二進制數 01100001 的十進制表示。其他的數字指令也一并用這種簡記法來轉寫。使用這樣的一種轉寫方法來寫程序,就是匯編語言(當然,這是一種極度簡化的說法)。匯編語言談不上太多設計,其實幾乎就是在直接告訴 CPU 應該做什么。把匯編語言轉化為機器碼的程序,稱為“匯編器(Assembler)“。
匯編語言的優勢是很低級,你能直接控制 CPU 的行為;匯編語言的缺點也是它太低級,你必須直接控制 CPU 的行為??纯础鞍?A 的值放進甲寄存器;B 的值放進乙寄存器;把乙寄存器的值放進 A;把甲寄存器的值放進 B?!边@段匯編指令執行后是什么結果?運行一下之后會看到,A 和 B 的值互換了。那么,能不能直接寫“交換變量 A 和 B 的值”,然后由計算機來分解為一串機器碼的組合呢?
所謂的“高級”編程語言就是這樣的原理。將高級編程語言翻譯成機器碼(或者其他更接近機器碼的形式)的過程,也就是計算機“理解”語言的過程,叫做“編譯”,而完成這一工作的程序,叫做“編譯器(compiler)”或者“解釋器(interpreter)”,兩者的區別是,編譯器一次性解析所有代碼并轉換成機器碼(但通常不會運行),而解釋器則每解析一小部分就運行一小部分。
接下來就要考慮兩個問題:高級語言要讓人寫起來方便;也要讓計算機易懂。因為人類是難搞的物種,所以前者通常是語言設計的重點。畢竟,只要懂些編程的基本知識,任何人都可以在三天時間里設計出一門計算機語言,并且讓計算機讀懂它(也就是寫出編譯器),但要讓一種計算機語言寫起來舒服、讀起來易懂、管理起來方便,所需耗費的心力和時間則相去不可以道里計。探尋這一問題的種種思潮所引發的范式轉換和生產力革命,是計算機歷史的永恒主題之一。計算機語言越來越高級,使用起來越來越簡單,實現卻越來越復雜;許多編程觀念比如面向對象(object orientation)、函數編程(functional programming)、事件驅動(event driven)之誕生、沉寂、重現、興盛和定型,都經由編程語言有所體現。
當然這并不是說編譯部分就不重要??煽?、高效、靈活的編譯器是一切編程工作的基石。我們日常所用的編譯器都是如此千錘百煉的東西,以至于你很少會意識到它們本身也是復雜的軟件工程項目,也有可能出問題,也在不斷地發展著。十年前和現在的編譯器,從架構理念到實現都有不小的差別。好在這種差別算不上天翻地覆,計算機語言編譯的大致過程一直都是如下幾個步驟:
高級語言的源代碼經過詞法分析(lexical analysis)成為一堆符記(token);
符記經過句法分析(syntactic analysis)成為語法樹(abstract syntax tree, AST);
語法樹經過優化,比如去除冗余的部分,最后映射成為機器碼(machine code);
第一步,詞法分析,根據的是語言設計者所規定的詞匯規則。比如 PHP 規定變量前頭必須加個 $ 符號,就是這樣的規則。通常通過正則表達式(regular expression)給出這些規則。根據規則來分析源代碼的編譯器組件叫做詞法分析器(lexer)或者掃描器(scanner)。掃描器可以自己手寫,也可以讓叫做 scanner generator 的程序讀取一個正則表達式,然后幫你生成一個 scanner。詞法分析的目的是判斷人類寫下的每個詞是不是合乎拼寫規則,如果不符的話,顯然也就無法編譯了。
第二步,句法分析,根據的是語言設計者所規定的語法規則。關于形式語言的語法理論,涉及到語言學和數理邏輯,是一個復雜而艱深的領域,好在對于設計一門計算機語言來說,只需要知道,計算機語言的語法通常是上下文無關文法(context-free grammar)即可:生成一條計算機語句的規則與這一規則所處的環境無關。這樣一來,解析一條編程語句的過程就是確定的無二的。根據規則來將上一步驟獲得的詞匯解析為特定的數據結構——比如語法樹——的工具,叫做句法分析器(parser)。同樣,句法分析器可以自己寫,也可以用特定的方法(最常見的是巴克斯范式(BNF))給出下上下文無關文法的形式語言描述,然后用所謂 parser generator 來生成。可以說,語法樹(或者類似的中間產物)代表了從編程語句中提煉出來的意義,這是整個編譯過程的核心所在。
第三步,語法樹或者其他的語義表示方法經由優化器(optimizer)的修剪,送入負責將特定結構轉化為目標機器代碼的程序生成可以運行的二進制程序。這一步必須考慮執行效率優化和目標架構的特點,與高級編程語言本身已經并無太大關聯了。
至此便可以開始設計編程語言了。
簡單來說,定義一門語言,有點像定義一個宇宙。一開始,宇宙空空如也。為了讓這個宇宙能夠開始運轉,并衍生出超越我們想象的復雜世界,我們作為“造物主”,需要準備好兩種東西:一是元素——就是提供一些基本的數據類型;二是規則——基本元素之間的運算法則。
以上兩種東西合起來,就是這門語言的“類型系統”。數學一點而言,就是定義一個基本集合,并定義在這個集合之上的運算。
這看起來沒什么了不起。常常被人們忽略,但事實上,我們復雜的世界本質上也不過是由一些簡單的基本單元基于一些基本規則運動的結果。語言設計也遵循這一原則。
可以說,類型系統是一門語言的核心。因為一門編程語言,本質而言,主要做兩件事情:一是描述信息;二是處理信息。
描述信息需要使用存儲空間,而處理信息需要使用運算。問題是運算本身對存儲空間會進行特別的解釋和假設,這就導致了我們的存儲空間盡管都是字節或之類的看起來別無二致的通用的空間,但從語言角度來看必須對這些空間進行特別的限定和理解,于是便產生了”類型“的概念。
例如C語言有double和long,本質而言兩種類型都是使用字節進行存儲。但由于運算時采取完全不同的解釋(甚至會采用cpu內部不同的運算器件),因此有必要對他們進行區分。
除了基本的類型系統之外,語言還需要有一些流程控制機制。和代碼組織機制。這些都需要設計??梢越梃b現有的編程語言,也可以創造獨一無二的新方式。隨你自由。
至于對象、變量什么的,其實之前設計類型系統的時候就已經涵蓋了。函數之類的在類型系統中也會涉及,同時也會影響代碼組織機制等等。
大家知道世界上最早的編程語言是什么嗎?一般認為是1954年開始開發的FORTRAN語言。
然后,仔細想想看,到底什么才是編程語言?如果將對機器的控制也看成是編寫“程序”的話,那么編程的起源便可以追溯到杰卡德織機上面所使用的打孔紙帶。
1801年,正值工業期間,杰卡德織機的發明使得提花編織的圖案可以通過“程序”來自動完成。從前在各個家庭中出現了自動紡織機,用于家庭作坊式的自動紡織生產,而杰卡德織機則相當于是這些家庭紡織機的放大版。我想那些自動紡織機應該也可以通過類似打孔紙帶的東西來輸入圖案,當然,最近的年輕人恐怕沒有親眼見過紡織機吧。
這種用打孔機來控制機器的想法,對各個領域都產生了影響。例如在英國從事通用計算機研發的查爾斯·巴貝奇,就在自制的“分析機”上用打孔紙帶來控制程序,遺憾的是,由于資金和其他一些問題,巴貝奇在生前未能將他的分析機制造出來。
不過,分析機的設計已經完成,用于分析機的程序也作為文檔保留了下來。協助開發這些程序的,是英國詩人拜倫之女愛達·洛夫萊斯,據說她和巴貝奇是師兄妹關系。如果不算分析機的設計者巴貝——那么世界上第一位程序員實際上是一位女性。為了紀念她,還有一種編程語言是以她的名字Ada命名。
在被稱為世界上第一臺計算機的ENIAC(1946年)中,程序不是用打孔紙帶,而是通過接電線的方式來輸入,讓人總覺得這是一種退步。
不過,無論是打孔紙帶,還是接電線,都不太可能實現太復雜的程序,真正的程序恐怕還要等到存儲程序式計算機出現以后。一般認為,世界上第一臺存儲程序式電子計算機,是1949年出現的EDSAC。
到了這個時候,所謂的“機器語言”就算正式問世了。當時的計算機程序都是用機器語言來縮寫的。那個時候不要說是編譯器,連匯編器都沒有發明出來呢,因此使用機器語言也就是理所當然的事情了。
說到底,機器語言就是一連串數字,將計算的步驟從指令表中查出對應的機器語言編碼,再人工寫成數列,這個工作可不容易?;蛘哒f,以前的人雖然沒有意識到,但從我們現代人的角度來看,這種辛苦簡直是難以置信。比如說,把引導程序的機器語言數列整個背下來,每次啟動的時候手動輸入進去;將機器語言指令表全部背下來,不用在紙上打草稿就能直接輸入機器語言指令并正確運行--“古代”的程序員們留下了無數的光輝事跡(或者是傳說),那時候的人們真是太偉大了。
然而有一天,有一個人忽然想到,查表這種工作本來應該是計算機最擅長的,那到讓計算機自己做不做好了嗎?于是,人們用更加容易記憶的指令(助記符)來代替數值,并開發了一種能夠自動生成機器語言的程序,這就是匯編器。
匯編器是用來解釋“匯編語言”的程序,匯編語言中所使用的助記符,和計算機指令是一一對應的關系。早期的計算機主要還是用于數值計算,因此數學才是主宰。在數學的世界里,數百年傳承下來的“語言”就是算式,因此用接近算式的形式來編寫計算機指令就顯得相當方便。隨后,FORTRAN于1954年問世了。FORTRAN這個名字的意思是:算式翻譯器(FORmula TRANslator).
也就是說,編程語言是由編程者根據自己的需要發明出來的。早期的計算機,由于性能不足、運算成本高,因此編寫和維護都被看成是非人的工作,而編程語言正是其開發擺脫非人性的象征。
其實,由助記符自動生成機器語言的匯編器,以及由人類較易懂的算式語句生成機器語言的編譯器,當時都被認為是革新性的技術,被稱為“自動編程”。此外,編譯器開發技術的研究甚至被視為人工智能研究的一部分。
未來的編程語言可能不會像過去的語言那樣,讓語言本身單獨存在,而是和編輯器、調試器、性能分析器等一切工具相互配合,以達到提高整體生產效率的目的。
計算機語言的發展過程
按照計算機語言的發展階段,可以分為機器語言、匯編語言和高級語言三類。
7解釋型、編譯型、混合型語言比較
8常用語言的應用領域
9 為什么這么多的語言?
語言設計人員設計的語言是為了解決特定的問題的目的而設計的(以用其編寫的程序應用于特定領域)
語言設計人員設計的語言在以下方面有側重點的取舍:編程簡單、程序易讀、執行效率高;
10 結構程序設計與面向對象程序設計
傳統的結構程序設計采取的方式是先考慮求解問題的算法,然后再尋找合適的數據結構。即傳統的結構程序是:程序=算法+數據結構。
面向對象的軟件開發思想認為程序是由對象組成的,而所有的這些程序代碼又都是是放在類中的。
傳統的過程化程序設計,必須從頂部的main函數開始編寫程序。在設計面向對象的系統時沒有所謂的頂部。而是從設計類開始,然后再往每個類中添加方法。
C語言是支持結構程序設計的語言,而C++既支持結構程序設計,同時也支持面向對象程序設計。
11 一個成功的編程語言必須滿足4個準則
需要建立一個明顯的社區。只有讓采用者安心,他才會去使用此技術;
需要具備可移植性,如Java虛擬機已經提高了后繼語言的門檻;
需要提供經濟上的動機,生產力、無線運算、數據搜索;
它需要展示技術優點;
如Java是一個很棒的靜態面向對象語言,具有可移植性及大量的API、產品、開放源碼項目,也是一個設計良好的語言和虛擬機。