初學程式設計之必備內功(上) (轉)

worldblog發表於2007-08-16
初學程式設計之必備內功(上) (轉)[@more@]

初學之必備內功(上)

:namespace prefix = o ns = "urn:schemas--com::office" />

1.是什麼?

 

簡言之,程式是用來描述、指導完成我們需要完成的任務的程式碼(廣義的程式還包括相關的執行所需的圖片、動畫、等資源)。比如,我要向螢幕列印一個字母“A”;或者要讓為我計算2+3的值,等等。由於時代、思維方式、程式設計任務以及個人喜好等因素的不同,出現了許許多多的描述方式,對這些不同的描述方式,我們就將它們區分開來,稱之為不同的語言。例如前面所說的,要向螢幕列印一個“A”字元,在BASIC語言中的對應描述是:

PRINT “A”

而下面則是Pascal下的描述:

writeln(‘A’);

再給出C語言的:

printf(“A”);

而C++則更傾向於下面的表述:

cout << “A”;

下面是:

System.out.print("A ");

以上,僅從輸出“A”字元這樣一個最基本的實現就可以看出,不同的語言之間往往有相當明顯的差別。當然,剛才我們所“領略”到的只是形式上的區別,而其背後的思想差異才是最重要的。

此外,程式語言還有高、低階之分。噢,不要望文生義地認為所謂的“低階”語言實現的功能會比“高階”語言實現的要少——恰恰相反,最“低階”的語言可以實現所有的功能,而那些太過“高階”的語言反而可能會有這樣那樣的限制。原來,這裡的高、低並不是指語言功能強大與否,而是指語言描述能力,或者說是語言與人類自然語言(思維方式)的接近程度。實際上,計算機唯一能“懂”的是最“低階”的機器語言,說白了就是像0101這樣的二進位制指令程式碼。但成天用0101來寫指令實在太累,也很容易出錯,所以就出現了相對“高階”一點的語言,它和機器語言基本上可以做到一一對應,但它的寫法是充滿了類似於mov、jmp這樣的助記符,而不是令人頭大的0101們。所以實在是方便了不少。當彙編程式碼寫好之後,再由一個可以自動轉換的程式碼的程式來將它們轉換成為可的二進位制程式碼。

雖然組合語言相對於機器語言來說著實是親切不少,但由於它很大程度上畢竟是機器語言的簡單對應式的替換,因此在編寫一些複雜的大型程式時尤其煩瑣;而由於其表達能力過於簡單,所以錯誤更是難以避免;此外,由於組合語言所對應的機器語言實際上可以看作是一系列對與較底層的操作,所以一旦換用不同的系統平臺或者機器,幾乎必須要進行修改甚至重新編寫。即使是像前面輸出“A”這樣一個簡單的操作,在和下就要分別寫兩個不同的彙編版本。其實我們所要求的功能是一樣的,但組合語言的抽象程度太低了,所以體現不出來。為此,人們又開發出各種高階程式語言,像Fortran、Ada、LISP、BASIC、Pascal、C/C++以及近年來出現的Java、等等。由於它們的抽象度比較高,無須與硬體、系統底層操作對應,所以移植性比彙編要好得多,理想的情況下甚至不必為不同的系統平臺或者機器改動原始碼。

自此,程式就分為了兩類,一類稱為“源程式”,就是程式設計師用各種相對高階一些的語言編寫的人們易於讀懂的的程式碼,包括前面提及的彙編程式碼,以及其它所有的高階程式語言編寫的程式碼;另一類則稱為“可執行程式”,就是由彙編程式碼或者高階語言程式碼經過程式的轉換後最終得到的二進位制程式,我們的可以直接識別並執行它,所以叫“可執行”。與之相應的概念,還有“原始碼”/“可執行程式碼”、“源”/“可執行檔案”等等。

你一定容易理解,“原始碼”就是指以彙編或者高階語言所編寫的程式碼,這些程式碼我們將它們以檔案的形式儲存起來,就成為原始檔。大多數的原始檔都是以最簡單的文字形式進行的,和我們常見的.txt檔案沒有區別,只不過為了表明它是原始檔,所以通常會起不同的副檔名,像BASIC語言為.bas,C語言為.c等等。因此,你可以用記事本這樣的簡單的文字編輯器對它們進行建立或者編輯。

2.從原始碼到執行

 

前面說過,計算機只認得二進位制的可執行程式碼,而我們更樂意使用匯編語言和各種高階語言。那麼,我們編寫的這些源程式最終是如何被執行的呢?

對於組合語言,前面已經提到過一個類似於轉換器的程式,它可以把我們寫的組合語言“翻譯”成機器語言,然後儲存在一個它生成的程式檔案中,就得到了我們所要的可執行程式。

而高階語言家族則非常豐富,所以對這個問題的解決也是八仙過海,各顯神通。大體分來,也不外乎兩大類:編譯型和解釋型。

我們先看看編譯型,其實它和前面的組合語言是一樣的:也是有一個負責翻譯的程式來對我們的原始碼進行轉換,生成相對應的可執行程式碼。這個過程說得專業一點,就稱為編譯(Compile),而負責編譯的程式自然就稱為(Compiler)。如果我們寫的程式程式碼都包含在一個原始檔中,那麼通常編譯之後就會直接生成一個可執行檔案,我們就可以直接執行了。但對於一個比較複雜的專案,為了方便管理,我們通常把程式碼分散在各個原始檔中,作為不同的模組來組織。這時編譯各個檔案時就會生成目標檔案( file)而不是前面說的可執行檔案。一般一個原始檔的編譯都會對應一個目標檔案。這些目標檔案裡的內容基本上已經是可執行程式碼了,但由於只是整個專案的一部分,所以我們還不能直接執行。待所有的原始檔的編譯都大功告成,我們就可以最後把這些半成品的目標檔案“打包”成一個可執行檔案了,這個工作由另一個程式負責完成,由於此過程好像是把包含可執行程式碼的目標檔案連線裝配起來,所以又稱為連結(Link),而負責連結的程式就叫……就叫連結程式(Linker)。連結程式除了連結目標檔案外,可能還有各種資源,像圖示檔案啊、聲音檔案啊什麼的,還要負責去除目標檔案之間的冗餘重複程式碼,等等,所以……也是挺累的。連結完成之後,一般就可以得到我們想要的可執行檔案了。

上面我們大概地介紹了編譯型語言的特點,現在再看看解釋型。噢,從字面上看,“編譯”和“解釋”的確都有“翻譯”的意思,它們的區別則在於翻譯的時機安排不大一樣。打個比方:假如你打算閱讀一本外文書,而你不知道這門外語,那麼你可以找一名翻譯,給他足夠的時間讓他從頭到尾把整本書翻譯好,然後把書的母語版交給你閱讀;或者,你也立刻讓這名翻譯輔助你閱讀,讓他一句一句給你翻譯,如果你想往回看某個章節,他也得重新給你翻譯。

兩種方式,前者就相當於我們剛才所說的編譯型:一次把所有的程式碼轉換成機器語言,然後寫成可執行檔案;而後者就相當於我們要說的解釋型:在程式執行的前一刻,還只有源程式而沒有可執行程式;而程式每執行到源程式的某一條指令,則會有一個稱之為解釋程式的外殼程式將原始碼轉換成二進位制程式碼以供執行,總言之,就是不斷地解釋、執行、解釋、執行……所以,解釋型程式是離不開解釋程式的。像早期的BASIC就是一門經典的解釋型語言,要執行BASIC程式,就得進入BASIC環境,然後才能載入程式原始檔、執行。解釋型程式中,由於程式總是以原始碼的形式出現,因此只要有相應的直譯器,移植幾乎不成問題。編譯型程式雖然原始碼也可以移植,但前提是必須針對不同的系統分別進行編譯,對於複雜的工程來說,的確是一件不小的時間消耗,況且很可能一些細節的地方還是要修改原始碼。而且,解釋型程式省卻了編譯的步驟,修改也非常方便,編輯完畢之後即可立即執行,不必像編譯型程式一樣每次進行小小改動都要耐心等待漫長的Compiling…Linking…這樣的編譯連結過程。不過凡事有利有弊,由於解釋型程式是將編譯的過程放到執行過程中,這就決定了解釋型程式註定要比編譯型慢上一大截,像幾百倍的速度差距也是不足為奇的。

編譯型與解釋型,兩者各有利弊。前者由於程式執行速度快,同等條件下對系統要求較低,因此像開發、大型應用程式、系統等時都採用它,像C/C++、Pascal/Object Pascal()、VB等基本都可視為編譯語言,而一些網頁尾本、指令碼及輔助開發介面這樣的對速度要求不高、對不同系統平臺間的相容性有一定要求的程式則通常使用解釋性語言,如Java、、、、等等。

但既然編譯型與解釋型各有優缺點又相互對立,所以一批新興的語言都有把兩者折衷起來的趨勢,例如Java語言雖然比較接近解釋型語言的特徵,但在執行之前已經預先進行一次預編譯,生成的程式碼是介於機器碼和Java原始碼之間的中介程式碼,執行的時候則由JVM(Java的虛擬機器平臺,可視為直譯器)解釋執行。它既保留了原始碼的高抽象、可移植的特點,又已經完成了對原始碼的大部分預編譯工作,所以執行起來比“純解釋型”程式要快許多。而像(或者以前版本)、C#這樣的語言,雖然表面上看生成的是.exe可執行程式檔案,但VB6編譯之後實際生成的也是一種中介碼,只不過編譯器在前面安插了一段自動某個外部直譯器的程式碼(該解釋程式獨立於編寫的程式,存放於系統的某個DLL檔案中,所有以VB6編譯生成的可執行程式都要用到它),以解釋執行實際的程式體。C#(以及其它的語言編譯器)則是生成.net目的碼,實際執行時則由.net解釋系統(就像JVM一樣,也是一個虛擬機器平臺)進行執行。當然.net目的碼已經相當“低階”,比較接近機器語言了,所以仍將其視為編譯語言,而且其可移植程度也沒有Java號稱的這麼強大,Java號稱是“一次編譯,到處執行”,而.net則是“一次編碼,到處編譯”。呵呵,當然這些都是題外話了。總之,隨著設計技術與硬體的不斷髮展,編譯型與解釋型兩種方式的界限正在不斷變得模糊。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-961202/,如需轉載,請註明出處,否則將追究法律責任。

相關文章