程式設計相關一覽
概論
‧程式語言簡介 - Introduction of Programming Language
‧直譯與編譯 - Interpretation and Compilation
‧控制流程敘述 - Control Flow Statement
‧變數是什麼 - What is Variable?
‧函式是什麼 - What is Function?
‧陣列是什麼 - What is Array?
‧指標是什麼 - What is Pointer?
介紹
‧新書「程式之美」
‧程式語言的「熱門」排行榜
雜記
‧漫談程式設計
‧由 TOI 回顧我的過往
‧莫忘初衷
演講
‧The Yahoo! User Interface Library
轉貼
‧程式高手的八大奧秘
‧勤勉之人、輕量之人
‧程設師遇到 BUG 的回應
‧資工系KTV必點歌曲
‧遊戲程設之路
‧明星程設師的十大特質
NOTICE
12月 05, 2008
【語言】指標是什麼 - What is Pointer?
在前面的文章中,我們已經瞭解到變數是什麼。而在這裡,我們要特別把一種特殊的變數獨立出來解釋。這種特殊的變數,就是所謂的指標(pointer)。
首先,我們知道:每一個變數都具有一個唯一的記憶體位址,以及一個變數的值。而所謂的指標,就代表其變數值所儲存的,是某個變數的記憶體位址。

那麼,指標有什麼用處呢?
或許你還記得,在先前我們曾經提到過的「陣列」。其實,陣列就是指標的一個實現。在使用陣列時,我們所使用的陣列名稱,實際上就是一個指標變數。
而這個指標所指向的,就是這一群陣列元素的最前端。而陣列的索引值則代表的是指標的偏移,也就是指標所指位置後的第幾個元素。
除此之外,指標也能夠在某些特定的資料結構(data structure)中,扮演控制存取資料元素,或是連結記憶體的角色。
例如在我曾經提過的堆疊、佇列、與連結串列中,指標都具有相當大的功用。
儘管指標如此便利,但也因此帶來一些不必要的問題。例如:我們可能會因為不當的指標偏移,使得程式不正確的覆蓋掉記憶體所存的值。即使是程式經驗的老手,也可能會因為指標的誤用,導致難以發現的程式錯誤。
也就因為如此,在某些程式語言(例如 Java)中,指標就被廢除、為其他方法所替代了。
首先,我們知道:每一個變數都具有一個唯一的記憶體位址,以及一個變數的值。而所謂的指標,就代表其變數值所儲存的,是某個變數的記憶體位址。
那麼,指標有什麼用處呢?
或許你還記得,在先前我們曾經提到過的「陣列」。其實,陣列就是指標的一個實現。在使用陣列時,我們所使用的陣列名稱,實際上就是一個指標變數。
而這個指標所指向的,就是這一群陣列元素的最前端。而陣列的索引值則代表的是指標的偏移,也就是指標所指位置後的第幾個元素。
除此之外,指標也能夠在某些特定的資料結構(data structure)中,扮演控制存取資料元素,或是連結記憶體的角色。
例如在我曾經提過的堆疊、佇列、與連結串列中,指標都具有相當大的功用。
儘管指標如此便利,但也因此帶來一些不必要的問題。例如:我們可能會因為不當的指標偏移,使得程式不正確的覆蓋掉記憶體所存的值。即使是程式經驗的老手,也可能會因為指標的誤用,導致難以發現的程式錯誤。
也就因為如此,在某些程式語言(例如 Java)中,指標就被廢除、為其他方法所替代了。
11月 27, 2008
【語言】陣列是什麼 - What is Array?
在前幾篇文章中,我們已經介紹過變數了。但是,假設現在出現了這種情況:現在班上有 30 個人,而你想要寫一個程式,記錄班上所有人的期中考成績,並計算出全班總平均。
於是,你可能會這麼寫:
喔,這樣的程式看起來真討厭!
若是現在班上不只 30 個人,而是 300 個人,你就得重複寫 300 次這種類似 "input scoreX" 這種敘述。無論對於撰寫或是閱讀而言,這種情況都是相當麻煩的一件事。
不過,我們可以利用陣列(array)來解決這個問題。
陣列所代表的是一串佔用連續記憶體的變數集合。我們可以只利用一個名稱(陣列名稱)來表示這些變數,以省去大量變數命名的麻煩。
而在存取時,我們只需要透過指定索引(index)值的方式,就可以取得陣列中的某個元素。換句話說,在某些情況下我們也可以利用迴圈(loop),來對整個陣列的元素進行處理。
例如,上面期中考成績的程式,我們可以利用陣列改寫成這樣:

由上面可以看到,我們可以直接利用 for 迴圈來依次取得使用者的輸入,並根據索引值 i 存進適當的陣列元素中,同時計算出全班的分數總和以得出平均分數。這樣的寫法是不是簡潔、俐落多了呢?
接下來,情況改變一下。若是你現在要記錄的不止是全班期中考成績,而是全年級 15 個班(假設人數皆為 30 人)的期中考成績,那麼該怎麼做呢?
別想得那麼難!其實我們只需要將 score 改成一個「陣列的陣列」,也就是一個二維陣列(two-dimensional array)就可以了:

外圍的 for 迴圈用來控制代表「班級」的索引值 i,內層的 for 迴圈用來控制代表「座號」的索引值 j。於是,我們便可以利用 score[i][j] 來存取 i 班 j 號同學的成績了。
那麼,要記錄全校 3 個年級的學生成績呢?要記錄全區域 10 所學校的學生成績呢?當然,你也可以建立出三維、四維等多維陣列,它們的想法都是差不多的。
於是,你可能會這麼寫:
input score1; input score2; input score3; ...... ...... input score30; average = score1 + score2 + score3 + ... + score30;
喔,這樣的程式看起來真討厭!
若是現在班上不只 30 個人,而是 300 個人,你就得重複寫 300 次這種類似 "input scoreX" 這種敘述。無論對於撰寫或是閱讀而言,這種情況都是相當麻煩的一件事。
不過,我們可以利用陣列(array)來解決這個問題。
陣列所代表的是一串佔用連續記憶體的變數集合。我們可以只利用一個名稱(陣列名稱)來表示這些變數,以省去大量變數命名的麻煩。
而在存取時,我們只需要透過指定索引(index)值的方式,就可以取得陣列中的某個元素。換句話說,在某些情況下我們也可以利用迴圈(loop),來對整個陣列的元素進行處理。
例如,上面期中考成績的程式,我們可以利用陣列改寫成這樣:
for (i = 1 to 30)
{
input score[i];
total = total + score[i];
}
average = total / 30;

由上面可以看到,我們可以直接利用 for 迴圈來依次取得使用者的輸入,並根據索引值 i 存進適當的陣列元素中,同時計算出全班的分數總和以得出平均分數。這樣的寫法是不是簡潔、俐落多了呢?
接下來,情況改變一下。若是你現在要記錄的不止是全班期中考成績,而是全年級 15 個班(假設人數皆為 30 人)的期中考成績,那麼該怎麼做呢?
別想得那麼難!其實我們只需要將 score 改成一個「陣列的陣列」,也就是一個二維陣列(two-dimensional array)就可以了:
for (i = 1 to 15)
{
for (j = 1 to 30)
{
input score[i][j];
total[i] = total[i] + score[i][j];
}
average[i] = total[i] / 30;
}

外圍的 for 迴圈用來控制代表「班級」的索引值 i,內層的 for 迴圈用來控制代表「座號」的索引值 j。於是,我們便可以利用 score[i][j] 來存取 i 班 j 號同學的成績了。
那麼,要記錄全校 3 個年級的學生成績呢?要記錄全區域 10 所學校的學生成績呢?當然,你也可以建立出三維、四維等多維陣列,它們的想法都是差不多的。
11月 25, 2008
【語言】控制流程敘述 - Control Flow Statement
對於程式語言而言,敘述通常被認為是一支程式的最小組成單位。例如簡單的指派(assignment) x = 5 或是斷言(assertion) a < b 都屬於一個敘述。
不過,其實程式語言總歸而言只具有三種敘述結構,被稱為控制流程(control flow)敘述。分別為循序(sequence)結構、條件(condition,或稱 selection)結構、與重複(iteration,或稱 repetition)結構。
循序結構相當直覺且簡單。由字面上解釋,即代表程式以由上而下、有順序性的方式一個敘述接著一個敘述執行。例如:
條件結構則是代表程式在執行時,會依據條件值來改變程式執行的順序。當滿足條件時,會執行某一敘述區塊,若條件不滿足時,則執行另一敘述區塊。例如常見的 if 與 switch 敘述:
至於重複結構,就是指反覆執行程式中的某一敘述區塊,直到符合某項條件(利用條件結構)為止。常見的重複結構則分為前測式迴圈(pre-test loop)、後測式迴圈(post-test loop)、與計數式迴圈(count-controlled loop)三種。
前測式迴圈是先測試條件。若條件為真,才執行敘述區塊。當敘述區塊執行完畢後,會再回到測試條件重新測試。若條件仍成立,則再一次執行敘述區塊。如此週而復始下去,直到條件不成立為止。例如常見的 while loop 敘述:
後測式迴圈與前測式迴圈類似。唯一的不同點為:其會先執行敘述區塊一次,然後再測試條件。若條件為真,則重複執行,直到條件不成立才會離開迴圈。因此,迴圈內的敘述至少會被執行一次。例如常見的 do-while loop 敘述:
而計數式迴圈則不同於兩者。其會利用一個計數器(counter)來記錄迴圈執行的次數,以用於可預測執行次數的情況。例如常見的 for loop 敘述:
其實,除了這三種結構之外,還有一個可以進行無條件跳躍的 goto 敘述。
所謂的無條件跳躍指令,指的是無條件將程式執行的順序改變,跳到指定的地方繼續執行。它不像前面提到的迴圈或選擇敘述必須經過判斷,才能決定程式執行的順序,因此威力非常的強大。
這樣的指令看似方便,卻容易造成程式難以閱讀,也難以除錯。例如:
因為這樣子的寫法,我們並不能夠清楚的知道:此時究竟是由哪一個 goto 跳到 label 的。所以一般來說,goto 這類無條件跳躍指令是不太被建議使用的。
不過,其實程式語言總歸而言只具有三種敘述結構,被稱為控制流程(control flow)敘述。分別為循序(sequence)結構、條件(condition,或稱 selection)結構、與重複(iteration,或稱 repetition)結構。
循序結構相當直覺且簡單。由字面上解釋,即代表程式以由上而下、有順序性的方式一個敘述接著一個敘述執行。例如:
print "這是第一行,程式會先執行這裏"; print "這是第二行,我會在第一行執行之後執行"; print "這是第三行,我會在第二行執行之後執行";
條件結構則是代表程式在執行時,會依據條件值來改變程式執行的順序。當滿足條件時,會執行某一敘述區塊,若條件不滿足時,則執行另一敘述區塊。例如常見的 if 與 switch 敘述:
input a, b;
if (a > b)
{
print "a 大於 b";
}
else if (a < b)
{
print "a 小於 b";
}
else
{
print "a 等於 b";
}
input select;
switch (select)
{
case 1:
print "你選了選項 1";
case 2:
print "你選了選項 2";
case 3:
print "你選了選項 3";
default:
print "你選了 1~3 以外的選項";
}
至於重複結構,就是指反覆執行程式中的某一敘述區塊,直到符合某項條件(利用條件結構)為止。常見的重複結構則分為前測式迴圈(pre-test loop)、後測式迴圈(post-test loop)、與計數式迴圈(count-controlled loop)三種。
前測式迴圈是先測試條件。若條件為真,才執行敘述區塊。當敘述區塊執行完畢後,會再回到測試條件重新測試。若條件仍成立,則再一次執行敘述區塊。如此週而復始下去,直到條件不成立為止。例如常見的 while loop 敘述:
input a, b;
while (a > b)
{
print a;
a = a + 1;
}
後測式迴圈與前測式迴圈類似。唯一的不同點為:其會先執行敘述區塊一次,然後再測試條件。若條件為真,則重複執行,直到條件不成立才會離開迴圈。因此,迴圈內的敘述至少會被執行一次。例如常見的 do-while loop 敘述:
input a, b;
do
{
print a;
a = a + 1;
} while (a > b);
而計數式迴圈則不同於兩者。其會利用一個計數器(counter)來記錄迴圈執行的次數,以用於可預測執行次數的情況。例如常見的 for loop 敘述:
input n;
for (i = 1 to n)
{
print i;
}
其實,除了這三種結構之外,還有一個可以進行無條件跳躍的 goto 敘述。
所謂的無條件跳躍指令,指的是無條件將程式執行的順序改變,跳到指定的地方繼續執行。它不像前面提到的迴圈或選擇敘述必須經過判斷,才能決定程式執行的順序,因此威力非常的強大。
這樣的指令看似方便,卻容易造成程式難以閱讀,也難以除錯。例如:
label: ...... ...... goto label; ...... ...... goto label; ...... ...... goto label;
因為這樣子的寫法,我們並不能夠清楚的知道:此時究竟是由哪一個 goto 跳到 label 的。所以一般來說,goto 這類無條件跳躍指令是不太被建議使用的。
【語言】函式是什麼 - What is Function?
開發程式時,或許在程式很小的情況,把所有功能通通寫在一個同區塊(block)中是一個可行的方法。但是,一旦程式的規模越變越大,若仍舊將所有內容都寫在一個區塊中,將會導致程式變得難以除錯與閱讀。

讓我們舉個例來說明,假設在某個程式中,你需要將幾個變數(variable)做初始化的動作。如以下所示:
而且,或許你在整個程式中不只要作一次類似的動作:
如此一來,程式不但看起來相當冗長,而且一旦程式有任何錯誤或是更動(例如,我們現在要將 "life = 3" 改成 "life = 5"),就需要一一將每個相同的部分做修改。萬一在修改時又有疏失,對於程式的維護與除錯來說,都是相當不方便的。
為了解決這種問題,我們可以將重複性高,或是邏輯概念類似的部分分離成一個函式(function),並在需要的地方呼叫(call)函式以執行函式內容。

函式又稱為副程式(subroutine),概念有點類似於數學上的「函數」,代表的是一串程式區段的集合。例如上面我們所提到的例子,就可以利用函式改寫成這樣:
首先,我們可以看到,一個基本的函式包含兩個部份:函式的名稱與主體。其中,"initialize"是這個函式的名稱。由上面的例子我們可以知道:當我們要執行一個函式時,是需要透過其名稱來做呼叫的。而函式主體,理所當然就是函式所包含的程式區段了。
除此之外,我們也可以傳入一些資料到函式裡。
例如現在有一個方程式 f(x) = 3x2 - 6x + 1,則利用函式可以這麼寫:
在這裡,x 稱之一個為參數(parameter),在意義上有點類似數學中代入函數的數值:我們可以在呼叫函式的同時,傳入適當的參數(變數或是常數),來得到代入之後的結果。
當然,在得出運算的結果之後,我們還必須把這個結果傳回去。而這個傳回的結果,我們稱之為返回(return)值。而在這個例子中,f() 函式的返回值就是 x 代入 3x2 - 6x + 1 之後得出的結果。
經過以上這些說明,我們可以知道:善加利用函式的方式,程式也會因此變得比較好維護。因為一旦程式有錯誤或是更動,我們不需要再一一更改所有相同的部份,只需要修改函式中的程式碼就可以了。
與之前提過的變數相同,函式的使用與規範也隨著程式語言而有所不同。因此大部分的細節這裡並不多提,讓我們留待之後再做解釋。

讓我們舉個例來說明,假設在某個程式中,你需要將幾個變數(variable)做初始化的動作。如以下所示:
score = 0; life = 3; item = 50; win = false; pause = false;
而且,或許你在整個程式中不只要作一次類似的動作:
...... ...... score = 0; life = 3; item = 50; win = false; pause = false; ...... ...... score = 0; life = 3; item = 50; win = false; pause = false; ...... ...... score = 0; life = 3; item = 50; win = false; pause = false; ...... ......
如此一來,程式不但看起來相當冗長,而且一旦程式有任何錯誤或是更動(例如,我們現在要將 "life = 3" 改成 "life = 5"),就需要一一將每個相同的部分做修改。萬一在修改時又有疏失,對於程式的維護與除錯來說,都是相當不方便的。
為了解決這種問題,我們可以將重複性高,或是邏輯概念類似的部分分離成一個函式(function),並在需要的地方呼叫(call)函式以執行函式內容。

函式又稱為副程式(subroutine),概念有點類似於數學上的「函數」,代表的是一串程式區段的集合。例如上面我們所提到的例子,就可以利用函式改寫成這樣:
function initialize()
{
score = 0;
life = 3;
item = 50;
win = false;
pause = false;
}
......
......
initialize();
......
......
initialize();
......
......
initialize();
......
......
首先,我們可以看到,一個基本的函式包含兩個部份:函式的名稱與主體。其中,"initialize"是這個函式的名稱。由上面的例子我們可以知道:當我們要執行一個函式時,是需要透過其名稱來做呼叫的。而函式主體,理所當然就是函式所包含的程式區段了。
除此之外,我們也可以傳入一些資料到函式裡。
例如現在有一個方程式 f(x) = 3x2 - 6x + 1,則利用函式可以這麼寫:
function f(x)
{
return 3 * x * x - 6 * x + 1;
}
print f(5);
在這裡,x 稱之一個為參數(parameter),在意義上有點類似數學中代入函數的數值:我們可以在呼叫函式的同時,傳入適當的參數(變數或是常數),來得到代入之後的結果。
當然,在得出運算的結果之後,我們還必須把這個結果傳回去。而這個傳回的結果,我們稱之為返回(return)值。而在這個例子中,f() 函式的返回值就是 x 代入 3x2 - 6x + 1 之後得出的結果。
經過以上這些說明,我們可以知道:善加利用函式的方式,程式也會因此變得比較好維護。因為一旦程式有錯誤或是更動,我們不需要再一一更改所有相同的部份,只需要修改函式中的程式碼就可以了。
與之前提過的變數相同,函式的使用與規範也隨著程式語言而有所不同。因此大部分的細節這裡並不多提,讓我們留待之後再做解釋。
11月 12, 2008
【語言】變數是什麼 - What is Variable?
變數(variable)是什麼?從字面上解釋,變數就是「可變的數」,也就是其值可以隨著時間而改變。假如用更精確的說法來解釋,程式設計上的變數,代表的就是一個擁有名稱的記憶體儲存空間。
或許你曾經在計算機概論,或是電腦原理的相關書籍中讀到過。我們可以將電腦的記憶體想像成一個一個小格子(cell)。其中的每一個格子都由 8 個存放著 0 或 1 的位元(bit)組成,稱之為一個位元組(byte)。
當然了,為了能夠對每一個記憶體空間進行儲存及存取的動作,因此每一個格子也都擁有一個絕對且唯一的地址(address)。

在機器語言或是組合語言這些低階語言中,我們可以直接利用記憶體的地址進行儲存及存取的動作。然而在高階語言的觀念,基於程式設計師撰寫與閱讀的方便上,我們並不希望如此(不管怎麼說,要記憶各個值的存放地址實在是太不直覺了)。
取而代之的,我們會給這些需要使用的記憶體空間一個「名字」,使我們能夠直接利用這個名字,對其所對應的記憶體進行操作。而這個擁有名字的記憶體,也就是所謂的「變數」了。
但是,不同類型的資料,例如表示字元(character)的變數與表示整數(integer)的變數,所使用到的記憶體空間大小可能就不盡相同。為了有效利用記憶體空間,也就有了各種不同的資料型態(data type)。
至於有哪些不同的資料型態,實際上是根據不同程式語言而有所差異的。所以關於變數,剩下的內容就留待介紹各種程式語言時再做說明了。
或許你曾經在計算機概論,或是電腦原理的相關書籍中讀到過。我們可以將電腦的記憶體想像成一個一個小格子(cell)。其中的每一個格子都由 8 個存放著 0 或 1 的位元(bit)組成,稱之為一個位元組(byte)。
當然了,為了能夠對每一個記憶體空間進行儲存及存取的動作,因此每一個格子也都擁有一個絕對且唯一的地址(address)。

在機器語言或是組合語言這些低階語言中,我們可以直接利用記憶體的地址進行儲存及存取的動作。然而在高階語言的觀念,基於程式設計師撰寫與閱讀的方便上,我們並不希望如此(不管怎麼說,要記憶各個值的存放地址實在是太不直覺了)。
取而代之的,我們會給這些需要使用的記憶體空間一個「名字」,使我們能夠直接利用這個名字,對其所對應的記憶體進行操作。而這個擁有名字的記憶體,也就是所謂的「變數」了。
但是,不同類型的資料,例如表示字元(character)的變數與表示整數(integer)的變數,所使用到的記憶體空間大小可能就不盡相同。為了有效利用記憶體空間,也就有了各種不同的資料型態(data type)。
至於有哪些不同的資料型態,實際上是根據不同程式語言而有所差異的。所以關於變數,剩下的內容就留待介紹各種程式語言時再做說明了。
11月 04, 2008
【語言】直譯與編譯 - Interpretation and Compilation
之前,我們提到過高階語言(high-level language)須經由轉換的動作,將原始的程式碼「翻譯」成機器看得懂的二進位機器碼。一般而言,我們可以因這種轉換的動作的不同,將程式語言分為編譯式語言(compiled language)和直譯式語言(interpreted language)兩種。
編譯式語言(如 C、C++、Pascal、Delphi 等)利用編譯器(compiler)針對原始程式先進行分析(analysis)以及前置處理(preprocess)的動作,並檢查程式中是否存在文法錯誤之後,再將之全部轉換為某種中介的目標語言(target language),稱之為目的檔(object file)。
將原始碼轉換為目的檔之後,我們還需要經由連結器(linker)連結一個或多個目的檔與外部函式庫(library),轉換成機器碼以形成可執行檔(executable file)。此後除非程式有所更改,否則不需要再次進行編譯的動作,便可以直接利用可執行檔執行使用了。
而直譯式語言(如 VB、Python、REBOL、Ruby 等)相對於編譯式語言,其執行前並不會產生任何目的檔或是可執行檔,而是在執行當中才利用直譯器(interpreter)將執行到的區塊進行解析(parse),再執行對應的機器碼。因此,其執行效率相較於編譯式語言是比較低的。
讓我們將高階語言比喻為英文、機器語言比喻為中文,以「演講」為範例描述兩種方式的區別。
若是一名演講者有一份英文的演說稿,但是必須以我們聽得懂的中文(假設我們只聽得懂中文)進行演說,則以「編譯」的方式,這名演講者必須先將整篇文章讀過,且全部翻譯成中文的演說稿(意義近似於前面提到的「可執行檔」)。此後,除非演講的內容改變,無論演講者作幾次演說,他都不需要將演講稿重新翻譯。因為他已經有中文的演說稿了!
而若以「直譯」的方式,這名演講者在演講前,並不會對這篇演講稿進行任何翻譯的動作。而是在實際演講時,才逐步翻譯目前演講到的內容。所以,這名演講者每次進行演說時,都需要將演講稿重新翻譯一次。
由以上這些,我們可以知道:由於編譯式語言需要一次將原始碼全部「翻譯」,因此會先花上較長的時間進行編譯的動作。而直譯式語言則因為執行時才將原始碼進行轉換,因此實際上的執行效率相較於編譯式語言是比較低的。
(對直譯的理解有誤,舉例不適當。感謝網友 mitnick 指正。)
當然,除了編譯式語言與直譯式語言之外,近來也出現了介於兩者之間的「混合式語言(hybrid language)」,例如著名的 Java 與 C#。
其原理為先將原始程式碼「編譯」成其虛擬機器(virtual machine,VM)的「機器語言」(這裡指的是虛擬機器的機器語言,不是實際上的機器語言)。等到要執行程式時,其再藉由虛擬機器「直譯」這些虛擬機器碼。理所當然的,這種程式語言的執行效率也就落在直譯式語言與編譯式語言之間。
然而,在某些主流平台(例如 x86)上,程式在第一次執行時可以透過即時編譯(just-in-time compilation,JIT)的方式,編譯成其所在平台的機器碼。經過這個步驟,其執行速度是可以相當於、甚至是優於編譯式語言的。(感謝網友 kgame 補充,mitnick 指正。)
而不論是編譯式語言、直譯式語言、還是混合式語言,都各擁有其優點與缺點。至於孰優孰劣,就只能視情況、見仁見智囉。
編譯式語言(如 C、C++、Pascal、Delphi 等)利用編譯器(compiler)針對原始程式先進行分析(analysis)以及前置處理(preprocess)的動作,並檢查程式中是否存在文法錯誤之後,再將之全部轉換為某種中介的目標語言(target language),稱之為目的檔(object file)。
將原始碼轉換為目的檔之後,我們還需要經由連結器(linker)連結一個或多個目的檔與外部函式庫(library),轉換成機器碼以形成可執行檔(executable file)。此後除非程式有所更改,否則不需要再次進行編譯的動作,便可以直接利用可執行檔執行使用了。
而直譯式語言(如 VB、Python、REBOL、Ruby 等)相對於編譯式語言,其執行前並不會產生任何目的檔或是可執行檔,而是在執行當中才利用直譯器(interpreter)將執行到的區塊進行解析(parse),再執行對應的機器碼。因此,其執行效率相較於編譯式語言是比較低的。
若是一名演講者有一份英文的演說稿,但是必須以我們聽得懂的中文(假設我們只聽得懂中文)進行演說,則以「編譯」的方式,這名演講者必須先將整篇文章讀過,且全部翻譯成中文的演說稿(意義近似於前面提到的「可執行檔」)。此後,除非演講的內容改變,無論演講者作幾次演說,他都不需要將演講稿重新翻譯。因為他已經有中文的演說稿了!
而若以「直譯」的方式,這名演講者在演講前,並不會對這篇演講稿進行任何翻譯的動作。而是在實際演講時,才逐步翻譯目前演講到的內容。所以,這名演講者每次進行演說時,都需要將演講稿重新翻譯一次。
由以上這些,我們可以知道:由於編譯式語言需要一次將原始碼全部「翻譯」,因此會先花上較長的時間進行編譯的動作。而直譯式語言則因為執行時才將原始碼進行轉換,因此實際上的執行效率相較於編譯式語言是比較低的。
(對直譯的理解有誤,舉例不適當。感謝網友 mitnick 指正。)
當然,除了編譯式語言與直譯式語言之外,近來也出現了介於兩者之間的「混合式語言(hybrid language)」,例如著名的 Java 與 C#。
其原理為先將原始程式碼「編譯」成其虛擬機器(virtual machine,VM)的「機器語言」(這裡指的是虛擬機器的機器語言,不是實際上的機器語言)。等到要執行程式時,其再藉由虛擬機器「直譯」這些虛擬機器碼。理所當然的,這種程式語言的執行效率也就落在直譯式語言與編譯式語言之間。
然而,在某些主流平台(例如 x86)上,程式在第一次執行時可以透過即時編譯(just-in-time compilation,JIT)的方式,編譯成其所在平台的機器碼。經過這個步驟,其執行速度是可以相當於
而不論是編譯式語言、直譯式語言、還是混合式語言,都各擁有其優點與缺點。至於孰優孰劣,就只能視情況、見仁見智囉。
11月 02, 2008
【語言】程式語言簡介 - Introduction of Programming Language
語言(language),時常是人與人之間賴以溝通的橋樑。正如日常生活中,三五好友間的哈拉閒聊、講台上教授的口述講解、或是我現在寫出來的這一篇文章,都是利用語言做為「你」與「我」之間,互相溝通、交流的管道。
同樣的,在電腦蓬勃發展的現代,我們也需要有一種方式,能夠與電腦做「溝通」,讓電腦能夠理解並遵循使用者的命令,以完成某些特定的「任務」。而程式語言(programming language)便是這種人與電腦溝通的「媒介」。
由於電腦其實只「看得懂」二進位(binary)數字(0 跟 1)。因此,在早期,程式設計師都是使用一連串 0 跟 1 所組成的機器語言(machine language)來撰寫程式。舉例來說:假設 "0010" 代表「相加(add)」,則 "0010 0001 0110" 這樣的一串指令,就表示著「把記憶體位置 0001 的值加上 6 (01102)」。
雖然因為這種方式是電腦可以直接理解的,所以使用機器語言撰寫的程式執行效率相當的高;但是,也正因為這串指令對人而言是如此的難以理解,且相當容易出錯。因此對人而言,使用機器語言來寫程式是相當費力的。
為了改善機器語言的缺點,組合語言(assembly language)誕生了。組合語言將機器語言的二進位指令,以一對一的方式轉換成簡單的符號或是英文的簡寫,使得指令更容易被人所記憶。於是,我們可以直接使用類似 "ADD 1 6" 的指令,來達成與前面提到的機器碼同樣的動作。
而在執行之前,只需要透過組譯器(assembler)將組合語言轉換為機器語言,電腦就看得懂了。雖然因為經過轉換的動作,組合語言的程式執行效率會比機器語言來的差一些,但是程式也因此變得好懂、好寫許多。
縱使如此,組合語言對一般人而言還是難以學習。而且,機器語言與組合語言都有一個缺點,就是他們都是「依賴於機器的(machine dependent)」。換句話說,可能你的機器語言(組合語言)程式移到別的機器上,它就不能執行了!
因此,相對於機器語言和組合語言這類低階語言(low-level language),高階語言(high-level language)出現了。(對電腦科學來說,高階與低階僅用於表達其「接近機器的程度」,並無優劣褒貶之意。)包含了常見的 BASIC、Pascal、Fortran、C、C++、Java 等等。
高階語言不像低階語言,其採用較接近人類所使用的自然語言(natural language),以人較容易閱讀理解的文法規則來編寫程式。雖然高階語言比起低階語言執行速度較慢,但是也因此減少了許多程式的學習難度與開發時間。
而且,高階語言在不同的平台上,都可以藉由不同平台上的編譯器(compiler)或直譯器(interpreter)被轉換為適當的機器語言,使得程式不再只因轉移平台,就需要將程式全部重新撰寫的情形。

自從高階語言出現以來,許許多多不同的程式概念與寫法不斷被提出、修正、改良。直到現今,仍然有許多代表著不同想法的高階語言不斷被創造出來。程式設計學術之盛,由此可見一斑。
同樣的,在電腦蓬勃發展的現代,我們也需要有一種方式,能夠與電腦做「溝通」,讓電腦能夠理解並遵循使用者的命令,以完成某些特定的「任務」。而程式語言(programming language)便是這種人與電腦溝通的「媒介」。
由於電腦其實只「看得懂」二進位(binary)數字(0 跟 1)。因此,在早期,程式設計師都是使用一連串 0 跟 1 所組成的機器語言(machine language)來撰寫程式。舉例來說:假設 "0010" 代表「相加(add)」,則 "0010 0001 0110" 這樣的一串指令,就表示著「把記憶體位置 0001 的值加上 6 (01102)」。
雖然因為這種方式是電腦可以直接理解的,所以使用機器語言撰寫的程式執行效率相當的高;但是,也正因為這串指令對人而言是如此的難以理解,且相當容易出錯。因此對人而言,使用機器語言來寫程式是相當費力的。
為了改善機器語言的缺點,組合語言(assembly language)誕生了。組合語言將機器語言的二進位指令,以一對一的方式轉換成簡單的符號或是英文的簡寫,使得指令更容易被人所記憶。於是,我們可以直接使用類似 "ADD 1 6" 的指令,來達成與前面提到的機器碼同樣的動作。
而在執行之前,只需要透過組譯器(assembler)將組合語言轉換為機器語言,電腦就看得懂了。雖然因為經過轉換的動作,組合語言的程式執行效率會比機器語言來的差一些,但是程式也因此變得好懂、好寫許多。
縱使如此,組合語言對一般人而言還是難以學習。而且,機器語言與組合語言都有一個缺點,就是他們都是「依賴於機器的(machine dependent)」。換句話說,可能你的機器語言(組合語言)程式移到別的機器上,它就不能執行了!
因此,相對於機器語言和組合語言這類低階語言(low-level language),高階語言(high-level language)出現了。(對電腦科學來說,高階與低階僅用於表達其「接近機器的程度」,並無優劣褒貶之意。)包含了常見的 BASIC、Pascal、Fortran、C、C++、Java 等等。
高階語言不像低階語言,其採用較接近人類所使用的自然語言(natural language),以人較容易閱讀理解的文法規則來編寫程式。雖然高階語言比起低階語言執行速度較慢,但是也因此減少了許多程式的學習難度與開發時間。
而且,高階語言在不同的平台上,都可以藉由不同平台上的編譯器(compiler)或直譯器(interpreter)被轉換為適當的機器語言,使得程式不再只因轉移平台,就需要將程式全部重新撰寫的情形。

自從高階語言出現以來,許許多多不同的程式概念與寫法不斷被提出、修正、改良。直到現今,仍然有許多代表著不同想法的高階語言不斷被創造出來。程式設計學術之盛,由此可見一斑。
訂閱:
文章 (Atom)
Infinite Loop © 2008. Blog design by Blogcut | Converted by Fernandooo1 (Randomness)