第1章 程式設計的方法

如夢發表於2014-01-24

本書的目的在於帶你象電腦科學家一樣思考。這種思考方式集數學、工程及自然科學之大成。象數學家一樣,電腦科學家使用形式語言表達他們的思想(特別是計算學)。象工程師一樣,電腦科學家進行設計,將元件組裝成系統,從備選方案中權衡利弊。象科學家一樣,電腦科學家觀察複雜系統的行為,形成假說,並對預言進行檢驗。

對電腦科學家來說,最為重要的技能是解決問題。解決問題意味著提出問題、創造性地思考解決方案並清晰地、精確地表述解決方案。事實證明,學習程式設計的過程對於實踐解決問題的技巧是個極佳的機遇。這就是本章為何稱作“程式設計的方法”。

一方面,將學習程式設計,這本身就是一種技能。另一方面,將把程式設計當作達到目標的手段。隨著深入,目標會變得愈加清晰。

1.1 Python程式語言

Python是本書要講述的程式語言。Python是高階語言的一例;或許也曾聽說過其他的高階語言,如C、C++、Perl和Java等。

低階語言有時也被稱為“機器語言”或者“組合語言”。一般來說,計算機只能執行用低階語言編寫的程式。因此,用高階語言編寫的程式在執行前需要進行處理。這一額外的處理過程會花費一些時間,這是高階語言小的缺點。

優點是多數的。首先,使用高階語言更容易程式設計。用高階語言編寫程式時,編寫佔用的時間較少,程式更短,更易於閱讀,而且它們的正確度更高。其次,高階語言是可移植的,這意味這它們稍作甚至不做修改就可在不同種類的計算機上執行。低階程式僅能在一種計算機上執行,為了在其他的計算機執行,不得不重新編寫。

基於這些優點,大部分的程式都用高階語言編寫。低階語言只用作一些特定的應用中。

兩種將高階語言處理成低階語言的程式是:直譯器編譯器。直譯器讀取高階語言程式並進行執行,也就是說程式叫它做什麼它就做什麼。它一次只處理程式的一點,交替地讀取程式碼行和執行運算。直譯器的結構如圖1.1所示。

圖1.1 直譯器一次只處理程式的一點,交替地讀取程式碼行和執行運算

圖1.1 直譯器一次只處理程式的一點,交替地讀取程式碼行和執行運算

編譯器讀取程式並在程式開始執行前將其完全翻譯。在這種模式中,高階程式也被稱為原始碼,翻譯後的程式稱為目的碼或者可執行程式碼。一旦編譯了程式,可以重複地執行而無需再翻譯。編譯器的結構圖1.2所示。

圖1.2 編譯器將原始碼翻譯成可被硬體執行器執行的目的碼

圖1.2 編譯器將原始碼翻譯成可被硬體執行器執行的目的碼

因為Python程式由直譯器執行,Python被認為是一種解釋性語言。使用直譯器有兩種方法:互動式指令碼式。在互動式中,鍵入Python程式,直譯器顯示結果:

>>> 1 + 1
2

符號>>>是直譯器的提示符,用來標明它已就緒。如果鍵入1 + 1,直譯器回答2

另外,可以將程式碼儲存在檔案中,再使用直譯器來執行檔案的內容,這樣的檔案也被稱為指令碼。依照慣例,Python指令碼的檔名以.py結尾。

要執行指令碼,需要告訴直譯器指令碼檔案的名稱。如果指令碼的名稱是dinsdale.py,工作在UNIX命令列視窗中,應該鍵入python dinsdale.py。在其他的開發環境中,執行指令碼的細節各異。對於自己的環境,可以在Python網站http://python.org查詢相應的指令。

在互動式中,因為鍵入後即可執行,所以便於測試小段程式碼。但對於多行的,應該將程式碼儲存作指令碼,以便日後的修改和執行。

1.2 程式是什麼?

程式是規範化如何執行計算的指令序列。計算可以數學的,諸如求解等式或者找出多項式的根之類,但也可以是符號計算,比如查詢和替換文件中的文字或者(最奇怪的是)編譯程式。

其細節因語言不同而不同,但在每種語言中,一些基本的指令都會出現:

  • 輸入:從鍵盤、檔案或者其他裝置獲取資料。
  • 輸出:在螢幕上顯示資料或者將資料傳送到檔案或者其他裝置。
  • 運算:執行基本的數學運算如加和乘。
  • 條件執行:檢查某種條件並執行相應的程式碼。
  • 重複:重複地執行某些動作,通常使用一些變數。

信不信由你,這些是所有的語言都有的。曾經使用過的每個程式,無論其是否複雜,都由以上這些構成指令。因此,可以將程式看作是將大的、複雜的任務,分解為越來越小的子任務,直到這些子任務足夠簡單以基本的指令方式執行。

也許有點模糊,但討論到演算法時將重回這一主題。

1.3 除錯是什麼?

程式是容易出錯的。出於古怪的原因,程式的錯誤被稱為缺陷bug),而且修正它們的過程被稱為除錯debug)。

在一個程式中會發生三類錯誤:語法錯誤、執行錯誤以及語義錯誤。為了快速地解決問題,區分它們是有益的。

1.3.1 語法錯誤

Python只在程式的語法正確時才執行它;否則,直譯器顯示出出錯資訊。語法指的是程式的結構以及關於這種結構的規則。比如,括號必須成對匹配,如(1 + 2)是合法的,但8)就是一個語法錯誤。

在英語中,讀者可能容忍大部分的語法錯誤,這就是我們為何可以讀卡明斯的詩歌而沒有吐出錯誤資訊的原因。Python不會如此寬巨集大量。只要程式中出現語法錯誤,不論它出現在何處,Python都將顯示出錯資訊並退出,而且不能執行該程式。初出茅廬的程式設計人員,可能要花費大量的時間去解決語法錯誤。隨著經驗的增長,幾乎很少出錯而且找出它們也更快。

1.3.2 執行時錯誤

第二類錯誤是執行時錯誤,如此稱呼是因為此類錯誤直到程式啟動執行時才出現。此類錯誤也被稱為異常。因為它們通常暗示發生了一些意料之外(而且是不好的)的事情。

執行時錯誤在本書開頭幾章的簡單程式中很少出現,遇到它們還要稍等片刻。

1.3.3 語義錯誤

第三類錯誤是語義錯誤。如果程式中有語義錯誤,就計算機不產生任何出錯資訊的意義上而言,它可以成功執行,但沒有做該做的事。它做了另一些。特別地,它沒有按要求的做。

問題在於已編寫的程式不是要想寫的程式。程式的含義(它的語義)是錯誤的。標識語義錯誤可能棘手些,因為它需要回溯程式的輸出並盡力找出所做的。

1.3.4 除錯的經驗

您所要具有的最重要的技能是除錯。雖然除錯可能令人沮喪,但它是最理性、最具挑戰性,也是程式設計時最有趣的一部分。

在某種意義上,除錯有點像偵破工作。要處理證據,要推斷導致所見結果的過程及事件。

除錯也如科學實驗。一旦有了關於如何出錯的思路,將修改程式並重試。如果假設是正確的,可以預測修改的結果,將逐步邁向可行的程式。如果假設是錯誤的,需要用一個新的替換它。如夏洛克•福爾摩斯(Sherlock Holmes)所言:“當排除掉所有的不可能,無論剩下什麼,也無論多麼不可能,必定是真相。”(A. Conan Doyle, The Sign of Four

對有些人而言,程式設計和除錯是同一件事。也就是說,程式設計是逐漸除錯程式直到它確實是所要的。在程式設計開始應抱有這樣的理念:程式處理一點並做小的修改,隨著深入,對它們進行除錯。這樣才能擁有可行的程式。

例如,Linux是一種包括成千上萬程式碼行的作業系統,但它開始時只是Linus Torvalds用來利用Intel 80386晶片的簡單程式。據Larry Greenfield說:“Linus早期的專案之一是在列印AAAA和BBBB之間進行切換的程式。它後來才演化成Linux。”(The Linux Users’ Guide Beta Version 1

後續的章節將對除錯和其他的程式設計活動給出更多的建議。

1.4 形式語言與自然語言

自然語言是人們日常用的語言,如英語、西班牙語和法語。它們不是人們設計的(雖然人們試圖給它們附加了一定的秩序);它們自然地演進。

形式語言是由人們為特定的應用而設計的語言。例如,數學家使用的符號就是一種形式語言,在表達數字和字元之間的關係方面是它的特長。化學家使用形式語言表達分子的化學結構。並且最重要的是:

程式語言是設計用來表示計算的形式語言。

形式語言通常有著嚴格的語法規則。例如,3 + 3 = 6是語法正確的數學語句,但3 + = 3 $ 6不是。H2O是正確的化學式,但2Zz不是。

語法規則有兩種型別,標記和結構。標記是基本的語言元素,如詞、數字和化學元素等等。對於3 + = 3 $ 6問題之一是$在數學中不是合法的標記(至少目前如此)。類似地,2Zz也不合法,因為沒有縮寫為Zz的元素。

語法規則的第二種型別是語句的結構。也就是說,安排標記的方式。語句3 + = 3是非法的,因為即使+和=是合法的標記,但不能一個緊跟在另一個後面。類似地,在化學式中下標出現在元素名稱之後,而不是之前。

習題 1 用英語寫出一個結構良好的句子,其中要含有無效的標記。接著另一個句子,其中含有的標記都有效但結構無效。

在閱讀英語中的句子或者形式語言中的語句時,要理解語句的結構是怎樣的(雖然在自然語言中,通常潛意識的這樣做)。這個過程稱為解析

例如,當聽到句子:“The penny dropped”(“終於明白了”),“the penny”應該理解為主語,“dropped”是謂語。一旦解析完語句,就能理解它的意思,或者說語句的語義。假如理解了“penny”的含義和“drop”的含義,就會理解該句通常的含義。

雖然形式語言和自然語言有許多共同的特徵——標記、結構、語法和語義,但也有些不同。

二義性。自然語言充滿了二義性,它是人們用語境和其他資訊表達的。形式語言被設計得幾乎或者完全無二義性,這意味這任何語句僅有一個意思,且不必考慮語境。

冗餘性。為了彌補二義性並減少誤解,自然語言使用了大量的冗餘。結果是,它們常常囉囉嗦嗦。形式語言很少冗餘而更加精確。

無修飾性。自然語言充滿了習慣用語和比喻。如果我說:“The penny dropped”,可能沒有硬幣而且也沒有掉落(這一習慣用語意思是終於明白了)。形式語言說的是什麼就是什麼。

人們說著自然語言長大——每個人——通常對自然語言的適應要經過一個艱難的過程。在某些方式上,形式語言和自然語言的不同與詩歌和散文的不同有些相似,但有著更多的不同。

詩歌。用詞表音也表意,並且詩歌整體上表現效果或者反映情感。二義性不僅是常見的,而且常常是故意的。

散文。詞語的文學意義更重要,結構更多是在表意方面。散文比詩歌更易於分析,但常常還是有二義性。

程式。計算機程式的意義是非二義性的和字面上的。通過分析標記和結構完全可以理解其意義。

這對閱讀程式也有所暗示。首先,要記住形式語言比自然語言更稠密,因此對懂它們要花更長的時間。而且,結構非常重要,因此從上到下、從左到右的閱讀通常不是好主意。相反,要學會在頭腦中解析程式,識別出標記,解釋出結構。最後,細節更重要。小的拼寫和發音錯誤,在自然語言中矇混過關,但在形式語言中有很大的不同。

1.5 第一個程式

使用新語言編寫的第一個程式通常稱為:“Hello, World!”,因為它所做的只是顯示:“Hello, World!”。在Python中,寫作:

print 'Hello, World!'

這是輸出語句的一個例子,它實際上不列印在紙面上。而是將值顯示在螢幕上。本例的輸出是:

Hello, World!

程式中的引號引在要顯示的文字的開頭和結尾;它們不出現在結果中。

在Python 3中,輸出的語法稍有不同。

print('Hello, World!')

括號表明print是個函式。在第3章將對函式進行介紹。

本書的剩餘部分,將使用第一種輸出語句。如果使用Python 3時,需要轉換。但除此之外,不必擔心有什麼不同。

1.6 除錯

在計算機面前閱讀本書是個好主意,這樣可以邊讀邊試本書的例子。多數示例以互動的模式執行,但如果把程式碼放在指令碼中,更容易嘗試不同的。

不管何時體驗新功能,都應該嘗試犯錯誤。例如,在“Hello, world!”程式,如果遺漏了一個引號會怎樣?如果兩個呢?如果拼錯了print會怎樣?

這類試驗有助於記住閱讀的內容;也可以求助於除錯,因為知道了錯誤訊息的含義。最好現在就故意犯個錯,這不同於以後或者偶然的錯誤。

程式設計,特別是除錯,有時顯示出強烈的感情色彩。如果在處理一個有難度的bug,可能會感到憤怒、沮喪或者尷尬。

有證據表明,人們對計算機自然的反應是把它們當人看。在它們執行得很好時,可以把它們看作是隊友;當它們固執和粗魯的時候,對它們的反應與對待粗魯、固執的人的方式相同(Reeves和Nass合著的The Media Equation: How People Treat Computers, Television, and New Media Like Real People and Places)。

對這些反應的準備,可能有助於處理它們。一種方法是把計算機看作是具有某種能力的員工,如速度和精確的能力,以及特殊的弱點,如缺乏同情心和無法掌控大局。

你的職責是當好管理員:找到揚長避短的方法。找到使用情感解決問題的方法,不讓你的反應干擾你有效處理的能力。

學習除錯可能令人沮喪的,但它是有價值的技巧,而它在程式設計之外的許多活動中也很有用。在每章結束,都有除錯的章節,如本章這樣,其中包含除錯的思想。希望它們有幫助!

1.7 詞彙表

問題的解決:確切地闡述問題、尋找解決方案以及確定解決方案的過程。

高階語言:像Python一樣被設計成易於閱讀和編寫的程式語言。

低階語言:被設計成易於計算機執行的程式語言;也稱為“機器語言”或者“組合語言”。

相容性:程式可以執行在多種計算機上的特性。

解釋:在高階語言中,通過將其逐行翻譯的方式,來執行程式。

編譯:將高階語言編寫的程式一次性全部翻譯為低階語言,而以後的執行做準備。

原始碼:在編譯前,用高階語言編寫的程式。

目的碼:在翻譯程式後,編譯器的輸出。

可執行:目的碼的另一個名稱,對執行已做好了準備。

提示符:有直譯器顯示的字元,表明已做好準備接收使用者的輸入。

指令碼:儲存在檔案中的程式(通常是將被解釋的)。

互動模式:通過在提示符處輸入命令和表示式,使用Python直譯器的方式。

指令碼模式:使用Python直譯器讀取和執行指令碼中語句的方式。

程式:規定了計算的指令集合。

演算法:為解決某類問題的一般過程。

bug:程式中的錯誤。

除錯:發現和移除所有的類程式錯誤的過程。

語法:程式的結構。

語法錯誤:程式中不可解析(因此也不可解釋)的錯誤。

異常:在程式執行時,發現的錯誤。

語義:程式的意義。

語義錯誤:程式中按照自己方式而不是程式設計人員意圖出現的錯誤。

自然語言:人們所言的自然演進的任何一種語言。

形式語言:為特定目的設計的任何一種語言。此類特定目的如表達數學思想和計算機程式。所有的程式語言都是形式語言。

標記:程式語法結構中的基本元素之一,與自然語言中的字類似。

解析:檢查程式並分析語法結構。

輸出語句:使Python直譯器在螢幕上輸出值的指令。

1.8 習題

習題 2 使用web瀏覽器訪問Python網站http://python.org/。它含有關於Python的資訊和與Python相關的頁面連結,並且也提供對Python文件的搜尋。

例如,如果在搜尋框中輸入print,第一個出現的連結是輸出語句的文件。從某種意義上說,不是每個都對你有幫助,但知道在何處是有益的。

習題 3 開啟Python直譯器,輸入help()來啟動聯機幫助工具。或者也可以輸入help('print')以獲取關於輸出語句的資訊。

如果本例無效,或許要安裝額外的Python文件或者設定環境變數;此細節依賴於你的作業系統和Python版本。

習題 4 開啟Python直譯器,把它用作計算器。對於數學運算Python的語法幾乎和標準的數學符號相同。例如,符號+-/和你所料的一樣分別表示加、減和除。乘的符號是*

如果43分30秒跑了10千米,每英里的平均時間是多少?平均速度是每小時多少英里?(提示:1英里=1.61千米)。

相關文章