解謎計算機科學(1)-----轉載自王垠的博客--http://www.yinwang.org/

解謎計算機科學(1)

第一章 - 初識計算

(本書版權歸王垠所有,禁止轉載。請認準 yinwang.org 爲唯一的閱讀地址,以獲得最近更新。)

要完全掌握一個學科的精髓,不能從細枝末節開始。一位哲人說過,人腦的能力受限於他自己的信念,當他不相信自己的時候,他就做不到本來可以的事情。人的信心是很重要的,它卻容易被挫敗。如果人看到很多細節卻看不到全景,他會失去信心,會覺得要到猴年馬月才能掌握一個學科。所以這本書不從細節開始,它也不會給你一個籠統的學科框架,告訴你這個學科包括這麼多的“分支”,每個分支又包括這麼多的“子分支”,它們多麼博大精深,每個分支都能出很多的專家學者……

我不想那樣嚇唬你。也許其它學科是博大精深的,但計算機科學… 大概不是 ;) 你可以在比較短的時間內抓住這個學科的精華,它可以整個裝進你的腦子裏。建立信心的一個有效方式,就是讓自己相信我已經掌握了整個學科,只不過我掌握的都是精華的原理。這些原理會指引我無往不勝,理解所有我遇到過或者沒遇到過的情況,看透所謂“新技術”背後的玄機。

所以我現在就告訴你這背後的原理。我會把計算機科學的基本概念和“子領域”的研究內容,用淺顯的例子跟你解釋。你會從一種微妙的方式體會到它們的本質。稍後我會把這些概念稍作發展,你就得到逐漸完整的把握。你一開始就掌握着整個學科,你會一直掌握着它,只不過逐漸增加更多的細節而已。這就像畫畫一樣,先勾勒出一個輪廓,一遍遍的改進加深自己的理解。這樣你就不會失去對大局的把握,被龐大複雜的印象嚇倒。

很多計算機專業的學生,雖然學了那麼多的課程,可是直到畢業都沒能回答一個最基礎的問題:什麼是計算?這一章會引導你去發現這個問題的答案。不要小看這個基礎問題,它是解決很多問題的關鍵。世界上有太多不理解它的人,他們走了很多的彎路,掉進很多的坑,製造出過度複雜或者有毛病的理論和技術。正是他們讓這個學科複雜不堪,讓人望而卻步。

接下來,我們就來理解幾個關鍵的概念,由此接觸到計算的本質。

自覺付費

跟本書的前言不同,這一章的內容不是免費的。你如果想繼續讀下去,請自覺進行付費。你的付款將會支持這本書的繼續寫作和更新。

手指算術

每個人都做過計算,只是大部分人都沒有理解自己在做什麼。回想一下幼兒園(大概四歲)的時候,媽媽問你:“幫我算一下,4+3 等於幾?” 你掰了一會手指,回答:7。當你掰手指的時候,你自己就是一臺簡單的計算機。

不要小看了這手指算術,它蘊含着深刻的原理。計算機科學植根於這類非常簡單的過程,而不是複雜的高等數學。現在我們來回憶一下這個過程。

  1. 當媽媽問你“4+3 等於幾”的時候,她是一個程序員,你是一臺計算機。你得到程序員的輸入:4,+,3。
  2. 聽到媽媽的問題之後,你拿出兩隻手,左手伸出四個指頭,右手伸出三個指頭。
  3. 接着你開始自己的計算過程。一根根地數那些豎起來的手指,每數一根你就把它彎下去,表示它已經被數過了。你念道:“1,2,3,4,5,6,7。”
  4. 現在已經沒有手指伸着,所以你把最後數到的那個數作爲答案:7!整個計算過程就結束了。

這裏應該有一段動畫,但現階段還沒有。我建議你自己演練一下以上的手指計算過程。

符號和模型

這裏的幼兒園手指算術包含着深刻的哲學問題,現在我們來初步體會一下這個問題。

當媽媽說“幫我算 4+3”的時候,4,+,3,三個字符傳到你耳朵裏,它們都是符號(symbol)。你可以把符號想象成一種表面特徵:光是盯着“4”和“3”這兩個阿拉伯數字的曲線,一個像旗子,一個像耳朵,你是不能做什麼的。你需要先用腦子把它們轉換成可以操作的“模型”(model)。這就是爲什麼你伸出兩隻手,一隻手錶示 4,另一隻表示 3。

這兩隻手的手勢是“可操作”的。你把左手再多彎曲一個手指,它就變成“3”。你再伸開一根手指,它就變成“5”。所以手指是一個相當好的模型,它是可以動,可操作的。把符號“4”和“3”轉換成手指模型之後,你就可以開始計算了。

你怎麼知道“4”和“3”對應什麼樣的手指模型呢?因爲媽媽以前教過你。十根手指,對應着 1 到 10 十個數。這就是爲什麼人都用十進制數做算術。

我們現在沒必要深究這個問題。我只是提示你,分清“符號”和“模型”是重要的。計算機科學和邏輯學裏的很多錯誤,過度複雜的理論和代碼,都是因爲沒有分清符號和模型。你現在可以不去深究這個問題,但總有一天你需要理解它。

計算圖

在計算機領域,我們經常用一些抽象的圖示來表達計算的過程,這樣就能直觀地看到信息的流動和轉換。這種圖示看起來是一些形狀用箭頭連接起來。我在這裏把它叫做“計算圖”。

對於以上的手指算術 4 + 3,我們可以用下圖來表示它:

圖中的箭頭表示信息的流動方向。說到“流動”,你可以想象一下水的流動。首先我們看到數字 4 和 3 流進了一個圓圈,圓圈裏有一個“+”號。這個圓圈就是你,一個會做手指加法的小孩。媽媽給你兩個數 4 和 3,你現在把它們加起來,得到 7 作爲結果。

注意圓圈的輸入和輸出方向是由箭頭決定的,我們可以根據需要調整那些箭頭的位置,只要箭頭的連接關係和方向不變就行。它們不一定都是從左到右,也可能從右到左或者從上到下,但“出入關係”都一樣:4 和 3 進去,結果 7 出來。比如它還可以是這樣:

我們用帶加號的圓圈表示一個“加法器”。顧名思義,加法器可以幫我們完成加法。在上個例子裏,你就是一個加法器。我們也可以用其他裝置作爲加法器,比如一堆石頭,一個算盤,某種電子線路…… 只要它能做加法就行。

具體要怎麼做加法,就像你具體如何掰手指,很多時候我們是不關心的,我們只需要知道這個東西能做加法就行。圓圈把具體的加法操作給“抽象化”了,這個藍色的圓圈可以代表很多種東西。抽象(abstraction)是計算機科學至關重要的思維方法,它幫助我們進行高層面的思考,而不爲細節所累。

表達式

計算機科學當然不止 4 + 3 這麼簡單,但它的基本元素確實是如此簡單。我們可以創造出很複雜的系統,然而歸根結底,它們只是在按某種順序計算像 4 + 3 這樣的東西。

4 + 3 是一個很簡單的表達式(expression)。你也許沒聽說過“表達式”這個詞,但我們先不去定義它。我們先來看一個稍微複雜一些的表達式:

2 * (4 + 3)

這個表達式比 4 + 3 多了一個運算,我們把它叫做“複合表達式”。這個表達式也可以用計算圖來表示:

你知道它爲什麼是這個樣子嗎?它表示的意思是,先計算 4 + 3,然後把結果(7)傳送到一個“乘法器”,跟 2 相乘,得到最後的結果。那正好就是 2 * (4 + 3) 這個表達式的含義,它的結果應該是 14。

爲什麼要先計算 4 + 3 呢?因爲當我們看到乘法器 2 * ... 的時候,其中一個輸入(2)是已知的,而另外一個輸入必須通過加法器的輸出得到。加法器的結果是由 4 和 3 相加得到的,所以我們必須先計算 4 + 3,然後才能與 2 相乘。

小學的時候,你也許學過:“括號內的內容要先計算”。其實括號只是“符號層”的東西,它並不存在於計算圖裏面。我這裏講的“計算圖”,其實才是本質的東西。數學的括號一類的東西,都只是表象,它們是符號或者叫“語法”。從某種意義上講,計算圖纔是表達式的本質或者“模型”,而“2 * (4 + 3)”這串符號,只是對計算圖的一種表示或者“編碼”(coding)。

這裏我們再次體會到了“符號”和“模型”的差別。符號是對模型的“表示”或者“編碼”。我們必須從符號得到模型,才能進行操作。這種從符號到模型的轉換過程,在計算機科學裏叫做“語法分析”(parsing)。我們會在後面的章節理解這個過程。

我們現在來給表達式做一個初步的定義。這並不是完整的定義,但你應該試着理解這種定義的方式。稍後我們會逐漸補充這個定義,逐漸完善。

定義(表達式):表達式可以是如下幾種東西。

  1. 數字是一個表達式。比如 1,2,4,15,……
  2. 表達式 + 表達式。兩個表達式相加,也是表達式。
  3. 表達式 - 表達式。兩個表達式相減,也是表達式。
  4. 表達式 * 表達式。兩個表達式相乘,也是表達式。
  5. 表達式 / 表達式。兩個表達式相除,也是表達式。

注意,由於我們之前講過的符號和模型的差別,爲了完全忠於我們的本質認識,這裏的“表達式 + 表達式”雖然看起來是一串符號,它必須被想象成它所對應的模型。當你看到“表達式”的時候,你的腦子裏應該浮現出它對應的計算圖,而不是一串符號。這個計算圖的畫面大概是這個樣子,其中左邊的大方框裏可以是任意兩個表達式。

是不是感覺這個定義有點奇怪?因爲在“表達式”的定義裏,我們用到了“表達式”自己。這種定義叫做“遞歸定義”。所謂遞歸(recursion),就是在一個東西的定義裏引用這個東西自己。看上去很奇怪,好像繞回去了一樣。遞歸是一個重要的概念,我們會在將來深入理解它。

現在我們可以來驗證一下,根據我們的定義,2 * (4 + 3) 確實是一個表達式:

  • 首先根據第一種形式,我們知道 4 是表達式,因爲它是一個數字。3 也是表達式,因爲它是一個數字。
  • 所以 4 + 3 是表達式,因爲 + 的左右都是表達式,它滿足表達式定義的第二種形式。
  • 所以 2 * (4 + 3) 是表達式,因爲 * 的左右都是表達式,它滿足表達式定義的第四種形式。

並行計算

考慮這樣一個表達式:

(4 + 3) * (1 + 2)

它對應一個什麼樣的計算圖呢?大概是這樣:

如果媽媽只有你一個小孩,你應該如何用手指算出它的結果呢?你大概有兩種辦法。

第一種辦法:先算出 4+3,結果是 7。然後算出 1+2,結果是 3。然後算 7*3,結果是 21。

第二種辦法:先算出 1+2,結果是 3。然後算出 4+3,結果是 7。然後算 7*3,結果是 21。

注意到沒有,你要麼先算 4+3,要麼先算 1+2,你不能同時算 4+3 和 1+2。爲什麼呢?因爲你只有兩隻手,所以算 4+3 的時候你就沒法算 1+2,反之也是這樣。總之,你媽媽只有你一個加法器,所以一次只能做一個加法。

現在假設你還有一個妹妹,她跟你差不多年紀,她也會手指算術。媽媽現在就多了一些辦法來計算這個表達式。她可以這樣做:讓你算 4+3,不等你算完,馬上讓妹妹算 1+2。等到你們的結果(7 和 3)都出來之後,讓你或者妹妹算 7*3。

發現沒有,在某一段時間之內,你和妹妹同時在做加法計算。這種時間上重疊的計算,叫做並行計算(parallel computing)。

你和妹妹同時計算,得到結果的速度可能會比你一個人算更快。如果你媽媽還有其它幾個孩子,計算複雜的式子就可能快很多,這就是並行計算潛在的好處。所謂“潛在”的意思是,這種好處不一定會實現。比如,如果你的妹妹做手指算數的速度比你慢很多,你做完了 4+3,只好等着她慢慢的算 1+2。這也許比你自己依次算 4+3 和 1+2 還要慢。

即使妹妹做算術跟你一樣快,這裏還有個問題。你和妹妹算出結果 7 和 3 之後,得把結果傳遞給下一個計算 7*3 的那個人(也許是你,也許是你妹妹)。這種“通信”會帶來時間的延遲,叫做“通信開銷”。如果你們其中一個說話慢,這比起一個人來做計算可能還要慢。

如何根據計算單元能力的不同和通信開銷的差異,來最大化計算的效率,降低需要的時間,就成爲了並行計算領域研究的內容。並行計算雖然看起來是一個“博大精深”的領域,可是你如果理解了我這裏說的那點東西,就很容易理解其餘的內容。

變量和賦值

如果你有一個複雜的表達式,比如

(5 - 3) * (4 + (2 * 3 - 5) * 6)

由於它有比較多的嵌套,人的眼睛是難以看清楚的,它要表達的意義也會難懂。這時候,你希望可以用一些“名字”來代表中間結果,這樣表達式就更容易理解。

打個比方,這就像你有一個親戚,他是你媽媽的表姐的女兒的丈夫。你不想每次都稱他“我媽媽的表姐的女兒的丈夫”,所以你就用他的名字“叮噹”來指代他,一下子就簡單了。

我們來看一個例子。之前的複合表達式

2 * (4 + 3)

其實可以被轉換爲等價的,含有變量的代碼:

{
    a = 4 + 3       // 變量 a 得到 4+3 的值
    2 * a           // 代碼塊的值
}

其中 a 是一個名字。a = 4 + 3 是一個“賦值語句”,它的意思是:用 a 來代表 4 + 3 的值。這種名字,計算機術語叫做變量(variable)。

這段代碼的意思可以簡單地描述爲:計算 4 + 3,把它的結果表示爲 a,然後計算 2 * a 作爲最後的結果。

有些東西可能擾亂了你的視線。兩根斜槓 // 後面一直到行末的文字叫做“註釋”,是給人看的說明文字。它們對代碼的邏輯不產生作用,執行的時候可以忽略。許多語言都有類似這種註釋,它們可以幫助閱讀的人,但是會被機器忽略。

這段代碼執行過程會是這樣:先計算 4 + 3 得到 7,用 a 記住這個中間結果 7。接着計算 2 * a ,也就是計算 2 * 7,所以最後結果是 14。很顯然,這跟 2 * (4 + 3) 的結果是一樣的。

a 叫做一個變量,它是一個符號,可以用來代表任意的值。除了 a,你還有許多的選擇,比如 b, c, d, x, y, foo, bar, u21… 只要它不會被誤解成其它東西就行。

如果你覺得這裏面的“神奇”成分太多,那我們現在來做更深一層的理解……

再看一遍上面的代碼。這整片代碼叫做一個“代碼塊”(block),或者叫一個“序列”(sequence)。這個代碼塊包括兩條語句,分別是 a = 4 + 3 和 2 * a。代碼塊裏的語句會從上到下依次執行。所以我們先執行 a = 4 + 3,然後執行 2 * a

最後一條語句 2 * a 比較特別,它是這個代碼塊的“值”,也就是最後結果。之前的語句都是在爲生成這個最後的值做準備。換句話說,這整個代碼塊的值就是 2 * a 的值。不光這個例子是這樣,這是一個通用的原理:代碼塊的最後一條語句,總是這個代碼塊的值。

我們在代碼塊的前後加上花括號 {...} 進行標註,這樣裏面的語句就不會跟外面的代碼混在一起。這兩個花括號叫做“邊界符”。我們今後會經常遇到代碼塊,它存在於幾乎所有的程序語言裏,只是語法稍有不同。比如有些語言可能用括號 (...) 或者 BEGIN...END 來表示邊界,而不是用花括號。

這片代碼已經有點像常用的編程語言了,但我們暫時不把它具體化到某一種語言。我不想固化你的思維方式。在稍後的章節,我們會把這種抽象的表達法對應到幾種常見的語言,這樣一來你就能理解幾乎所有的程序語言。

另外還有一點需要注意,同一個變量可以被多次賦值。它的值會隨着賦值語句而改變。舉個例子:

{
    a = 4 + 3
    b = a
    a = 2 * 5
    c = a
}

這段代碼執行之後,b 的值是 7,而 c 的值是 10。你知道爲什麼嗎?因爲 a = 4 + 3 之後,a 的值是 7。b = a 使得 b 得到值 7。然後 a = 2 * 5 把 a 的值改變了,它現在是 10。所以 c = a 使得 c 得到 10。

對同一個變量多次賦值雖然是可以的,但通常來說這不是一種好的寫法,它可能引起程序的混淆,應該儘量避免。只有當變量表示的“意義”相同的時候,你才應該對它重複賦值。

編譯

一旦引入了變量,我們就可以不用複合表達式。因爲你可以把任意複雜的複合表達式拆開成“單操作算術表達式”(像 4 + 3 這樣的),使用一些變量記住中間結果,一步一步算下去,得到最後的結果。

舉一個複雜點的例子,也就是這一節最開頭的那個表達式:

(5 - 3) * (4 + (2 * 3 - 5) * 6)

它可以被轉化爲一串語句:

{
    a = 2 * 3
    b = a - 5
    c = b * 6
    d = 4 + c
    e = 5 - 3
    e * d
}

最後的表達式 e * d,算出來就是原來的表達式的值。你觀察一下,是不是每個操作都非常簡單,不包含嵌套的複合表達式?你可以自己驗算一下,它確實算出跟原表達式一樣的結果。

在這裏,我們自己動手做了“編譯器”(compiler)的工作。通常來說,編譯器是一種程序,它的任務是把一片代碼“翻譯”成另外一種等價形式。這裏我們沒有寫編譯器,可是我們自己做了編譯器的工作。我們手動地把一個嵌套的複合表達式,編譯成了一系列的簡單算術語句。

這些語句的結果與原來的表達式完全一致。這種保留原來語義的翻譯過程,叫做編譯(compile)。

我們爲什麼需要編譯呢?原因有好幾種。我不想在這裏做完整的解釋,但從這個例子我們可以看到,編譯之後我們就不再需要複雜的嵌套表達式了。我們只需要設計很簡單的,只會做單操作算術的機器,就可以算出複雜的嵌套的表達式。實際上最後這段代碼已經非常接近現代處理器(CPU)的彙編代碼(assembly)。我們只需要多加一些轉換,它就可以變成機器指令。

我們暫時不寫編譯器,因爲你還缺少一些必要的知識。這當然也不是編譯技術的所有內容,它還包含另外一些東西。但從這一開頭,你就已經初步理解了編譯器是什麼,你只需要在將來加深這種理解。

函數

到目前爲止,我們做的計算都是在已知的數字之上,而在現實的計算中我們往往有一些未知數。比如我們想要表達一個“風扇控制器”,有了它之後,風扇的轉速總是當前氣溫的兩倍。這個“當前氣溫”就是一個未知數。

我們的“風扇控制器”必須要有一個“輸入”(input),用於得到當前的溫度 t,它是一個溫度傳感器的讀數。它還要有一個輸出,就是溫度的兩倍。

那麼我們可以用這樣的方式來表達我們的風扇控制器:

t -> t*2

不要把這想成任何一種程序語言,這只是我們自己的表達法。箭頭 -> 的左邊表示輸入,右邊表示輸出,夠簡單吧。

你可以把 t 想象成從溫度傳感器出來的一根電線,它連接到風扇控制器上,風扇控制器會把它的輸入(t)乘以 2。這個畫面像這個樣子:

我們談論風扇控制器的時候,其實不關心它的輸入是哪裏來的,輸出到哪裏去。如果我們把溫度傳感器和風扇從畫面裏拿掉,就變成這個樣子:

這幅圖纔是你需要認真理解的函數的計算圖。你發現了嗎,這幅圖畫正好對應了之前的風扇控制器的符號表示:t -> t*2。看到符號就想象出畫面,你就得到了符號背後的模型。

像 t -> t*2 這樣具有未知數作爲輸入的構造,我們把它叫做函數(function)。其中 t 這個符號,叫做這個函數的參數。

參數,變量和電線

你可能發現了,函數的參數和我們之前瞭解的“變量”是很類似的,它們都是一個符號。之前我們用了 a, b, c, d, e 現在我們有一個 t,這些名字我們都是隨便起的,只要它們不要重複就好。如果名字重複的話,可能會帶來混淆和干擾。

其實參數和變量這兩種概念不只是相似,它們的本質就是一樣的。如果你深刻理解它們的相同本質,你的腦子就可以少記憶很多東西,而且它可能幫助你對代碼做出一些有趣而有益的轉化。在上一節你已經看到,我用“電線”作爲比方來幫助你理解參數。你也可以用同樣的方法來理解變量。

比如我們之前的變量 a

{
    a = 4 + 3
    2 * a
}

它可以被想象成什麼樣的畫面呢?

我故意把箭頭方向畫成從右往左,這樣它就更像上面的代碼。從這個圖畫裏,你也許可以看到變量 a 和風扇控制器圖裏的參數 t,其實沒有任何本質差別。它們都表示一根電線,那根電線進入乘法器,將會被乘以 2,然後輸出。如果你把這些都看成是電路,那麼變量 a 和參數 t 都代表一根電線而已。

然後你還發現一個現象,那就是你可以把 a 這個名字換成任何其它名字(比如 b),而這幅圖不會產生實質的改變。

這說明什麼問題呢?這說明以下的代碼(把 a 換成了 b)跟之前的是等價的:

{
    b = 4 + 3
    2 * b
}

根據幾乎一樣的電線命名變化,你也可以對之前的函數得到一樣的結論:t -> t*2 和 u -> u*2,和 x -> x*2 都是一回事。

名字是很重要的東西,但它們具體叫什麼,對於機器並沒有實質的意義,只要它們不要相互混淆就可以。但名字對於人是很重要的,因爲人腦沒有機器那麼精確。不好的變量和參數名會導致代碼難以理解,引起程序員的混亂和錯誤。所以通常說來,你需要給變量和參數起好的名字。

什麼樣的名字好呢?我會在後面集中講解。

有名字的函數

既然變量可以代表“值”,那麼一個自然的想法,就是讓變量代表函數。所以就像我們可以寫

a = 4 + 3

我們似乎也應該可以寫

f = t -> t*2

對的,你可以這麼做。f = t->t*2 還有一個更加傳統的寫法,就像數學裏的函數寫法:

f(t) = t*2

請仔細觀察 t 的位置變化。我們在函數名字的右邊寫一對括號,在裏面放上參數的名字。

注意,你不可以只寫

f = t*2

你必須明確的指出函數的參數是什麼,否則你就不會明白函數定義裏的 t 是什麼東西。明確指出 t 是一個“輸入”,你纔會知道它是函數的輸入,是一個未知數,而不是在函數外面定義的其它變量

這個看似簡單的道理,很多數學家都不明白,所以他們經常這樣寫書:

有一個函數 y = x*2

這是錯誤的,因爲他沒有明確指出“x 是函數 y 的參數”。如果這句話之前他們又定義過 x,你就會疑惑這是不是之前那個 x。很多人就是因爲這些糊里糊塗的寫法而看不懂數學書。這不怪他們,只怪數學家自己對於語言不嚴謹。

函數調用

有了函數,我們可以給它起名字,可是我們怎麼使用它的值呢?

由於函數裏面有未知數(參數),所以你必須告訴它這些未知數,它裏面的代碼纔會執行,給你結果。比如之前的風扇控制器函數

f(t) = t*2

它需要一個溫度作爲輸入,纔會給你一個輸出。於是你就這樣給它一個輸入:

f(2)

你把輸入寫在函數名字後面的括號裏。那麼你就會得到輸出:4。也就是說 f(2)的值是 4。

如果你沒有調用一個函數,函數體是不會被執行的。因爲它不知道未知數是什麼,所以什麼事也做不了。那麼我們定義函數的時候,比如

f(t) = t*2

當看到這個定義的時候,機器應該做什麼呢?它只是記錄下:有這麼一個函數,它的參數是 t,它需要計算 t*2,它的名字叫 f。但是機器不會立即計算 t*2,因爲它不知道 t 是多少。

分支

直到現在,我們的代碼都是從頭到尾,悶頭悶腦地執行,不問任何問題。我們缺少一種“問問題”的方法。比如,如果我想表達這樣一個“食物選擇器”:如果氣溫低於 22 度,就返回 “hotpot” 表示今天吃火鍋,否則返回 “ice cream” 表示今天吃冰激凌。

我們可以把它圖示如下:

中間這種判斷結構叫做“分支”(branching),它一般用菱形表示。爲什麼叫分支呢?你想象一下,代碼就像一條小溪,平時它沿着一條路線流淌。當它遇到一個棱角分明的大石頭,就分成兩個支流,分開流淌。

我們的判斷條件 t < 22 就像一塊大石頭,我們的“代碼流”碰到它就會分開成兩支,分別做不同的事情。跟溪流不同的是,這種分支不是隨機的,而是根據條件來決定,而且分支之後只有一支繼續執行,而另外一邊不會被執行。

我們現在看到的都是圖形化表示的模型,爲了書寫方便,現在我們要從符號的層面來表示這個模型。我們需要一種符號表示法來表達分支,我們把它叫做 if(如果)。我們的飲料選擇器代碼可以這樣寫:

t -> if (t < 22) 
     {
       "hotpot"
     }
     else 
     {
       "ice cream"
     }

它是一個函數,輸入是一個溫度。if 後面的括號裏放我們的判斷條件。後面接着條件成立時執行的代碼塊,然後是一個 else,然後是條件不成立時執行的代碼。它說:如果溫度低於 22 度,我們就吃火鍋,否則就吃冰激凌。

其中的 else 是一個特殊的符號,它表示“否則”。看起來不知道爲什麼 else 要在那裏?對的,它只是一個裝飾品。我們已經有足夠的表達力來分辨兩個分支,不過有了 else 似乎更加好看一些。很多語言裏面都有 else 這個標記詞在那裏,所以我也把它放在那裏。

這只是一個最簡單的例子,其實那兩個代碼塊裏面不止可以寫一條語句。你可以有任意多的語句,就像這樣:

t ->
if (t < 22)
{
    a = 4 + 3
    b = a * 2
    "hotpot"
}
else
{
    x = "ice cream"
    x
}

這段代碼和之前是等價的,你知道爲什麼嗎?

字符串

上面一節出現了一種我們之前沒見過的東西,我爲了簡潔而沒有介紹它。這兩個分支的結果,也就是加上引號的 “hotpot” 和 “ice cream”,它們並不是數字,也不是其它語言構造,而是一種跟數字處於幾乎同等地位的“數據類型”,叫做字符串(string)。字符串是我們在計算機裏面表示人類語言的基本數據類型。

關於字符串,在這裏我不想講述更加細節的內容,我把對它的各種操作留到以後再講,因爲雖然字符串對於應用程序很重要,它卻並不是計算機科學最關鍵最本質的內容。

很多計算機書籍一開頭就講很多對字符串的操作,導致初學者費很大功夫去做很多打印字符串的練習,結果幾個星期之後還沒學到“函數”之類最根本的概念。這是非常可惜的。

布爾值

我們之前的 if 語句的條件 t < 22 其實也是一個表達式,它叫做“布爾表達式”。你可以把小於號 < 看成是跟加法一類的“操作符”。它的輸入是兩個數值,輸出是一個“布爾值”。什麼是布爾值呢?布爾值只有兩個:true 和 false,也就是“真”和“假”。

舉個例子,如果 t 的值是 15,那麼 t < 22 是成立的,那麼它的值就是 true。如果 t 的值是 23,那麼 t < 22 就不成立,那麼它的值就是 false。是不是很好理解呢?

我們爲什麼需要“布爾值”這種東西呢?因爲它的存在可以簡化我們的思維。對於布爾值也有一些操作,這個我也不在這一章贅述,放到以後細講。

計算的要素

好了,現在你已經掌握了計算機科學的幾乎所有基本要素。每一個編程語言都包括這些構造:

  1. 基礎的數值。比如整數,字符串,布爾值等。
  2. 表達式。包括基本的算術表達式,嵌套的表達式。
  3. 變量和賦值語句。
  4. 分支語句。
  5. 函數和函數調用。

你也許可以感覺到,我是把這些構造按照“從小到大”的順序排列的。這也許可以幫助你的理解。

現在你可以回想一下你對它們的印象。每當學習一種新的語言或者系統,你只需要在裏面找到對應的構造,而不需要從頭學習。這就是掌握所有程序語言的祕訣。這就像學開車一樣,一旦你掌握了油門,剎車,換擋器,方向盤,速度表的功能和用法,你就學會了開所有的汽車,不管它是什麼型號的汽車。

我們在這一章不僅理解了這些要素,而且爲它們定義了一種我們自己的“語言”。顯然這個語言只能在我們的頭腦裏運行,因爲我們沒有實現這個語言的系統。在後面的章節,我會逐漸的把我們這種語言映射到現有的多種語言裏面,然後你就能掌握這些語言了。

但是請不要以爲掌握了語言就學會了編程或者學會了計算機科學。掌握語言就像學會了各種汽車部件的工作原理。幾分鐘之內,初學者就能讓車子移動,轉彎,停止。可是完了之後你還需要學習交通規則,你需要許許多多的實戰練習和經驗,掌握各種複雜情況下的策略,才能成爲一個合格的駕駛員。如果你想成爲賽車手,那就還需要很多倍的努力。

但是請不要被我這些話嚇到了,你沒有那麼多的競爭者。現在的情況是,世界上就沒有很多合格的計算機科學駕駛員,更不要說把車開得流暢的賽車手。絕大部分的“程序員”連最基本的引擎,油門,剎車,方向盤的工作原理都不明白,思維方式就不對,所以根本沒法獨自上路,一上路就出車禍。很多人把過錯歸結在自己的車身上,以爲換一輛車馬上就能成爲好的駕駛員。這是一種世界範圍的計算機教育的失敗。

在後面的章節,我會引導你成爲一個合格的駕駛員,隨便拿一輛車就能開好。

什麼是計算

現在你掌握了計算所需要的基本元素,可是什麼是計算呢?我好像仍然沒有告訴你。這是一個很哲學的問題,不同的人可能會告訴你不同的結果。我試圖從最廣義的角度來告訴你這個問題的答案。

當你小時候用手指算 4+3,那是計算。如果後來你學會了打算盤,你用算盤算 4+3,那也是計算。後來你從我這裏學到了表達式,變量,函數,調用,分支語句…… 在每一新的構造加入的過程中,你都在瞭解不同的計算。

所以從最廣義來講,計算就是“機械化的信息處理”。所謂機械化,你可以用手指算,可以用算盤,可以用計算器,或者計算機。這些機器裏面可以有代碼,也可以沒有代碼,全是電子線路,甚至可以是生物活動或者化學反應。不同的機器也可以有不同的計算功能,不同的速度和性能……

有這麼多種計算的事實不免讓人困惑,總害怕少了點什麼,其實你可以安心。如果你掌握了上一節的“計算要素”,那麼你就掌握了幾乎所有類型的計算系統所需要的東西。你在後面所需要做的只是加深這種理解,並且把它“對應”到現實世界遇到的各種計算機器裏面。

爲什麼你可以相信計算機科學的精華就只有這些呢?因爲計算就是處理信息,信息有它誕生的位置(輸入設備,固定數值),它傳輸的方式(賦值,函數調用,返回值),它被查看的地方(分支)。你想不出對於信息還有什麼其它的操作,所以你就很安心的相信了,這就是計算機科學這種“棋類遊戲”的全部規則。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章