NOTICE

 任何跟文章無關的閒聊,請愛用 留言板(Guestbook)。

 想要快速瀏覽主題,請點選單 目錄 標籤。

 停止更新ing,請見諒。 <(_ _)>


顯示具有 語言 標籤的文章。 顯示所有文章
顯示具有 語言 標籤的文章。 顯示所有文章

12月 05, 2008

【目錄】程式設計

@
程式設計相關一覽


概論

‧程式語言簡介 - 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必點歌曲
‧遊戲程設之路
‧明星程設師的十大特質

【語言】指標是什麼 - What is Pointer?

@
  在前面的文章中,我們已經瞭解到變數是什麼。而在這裡,我們要特別把一種特殊的變數獨立出來解釋。這種特殊的變數,就是所謂的指標(pointer)。

  首先,我們知道:每一個變數都具有一個唯一的記憶體位址,以及一個變數的值。而所謂的指標,就代表其變數值所儲存的,是某個變數的記憶體位址。




  那麼,指標有什麼用處呢?

  或許你還記得,在先前我們曾經提到過的「陣列」。其實,陣列就是指標的一個實現。在使用陣列時,我們所使用的陣列名稱,實際上就是一個指標變數。

  而這個指標所指向的,就是這一群陣列元素的最前端。而陣列的索引值則代表的是指標的偏移,也就是指標所指位置後的第幾個元素。


  除此之外,指標也能夠在某些特定的資料結構(data structure)中,扮演控制存取資料元素,或是連結記憶體的角色。

  例如在我曾經提過的堆疊、佇列、與連結串列中,指標都具有相當大的功用。


  儘管指標如此便利,但也因此帶來一些不必要的問題。例如:我們可能會因為不當的指標偏移,使得程式不正確的覆蓋掉記憶體所存的值。即使是程式經驗的老手,也可能會因為指標的誤用,導致難以發現的程式錯誤。

  也就因為如此,在某些程式語言(例如 Java)中,指標就被廢除、為其他方法所替代了。

11月 27, 2008

【語言】陣列是什麼 - What is Array?

@
  在前幾篇文章中,我們已經介紹過變數了。但是,假設現在出現了這種情況:現在班上有 30 個人,而你想要寫一個程式,記錄班上所有人的期中考成績,並計算出全班總平均。

  於是,你可能會這麼寫:

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)結構。

  循序結構相當直覺且簡單。由字面上解釋,即代表程式以由上而下、有順序性的方式一個敘述接著一個敘述執行。例如:

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)做初始化的動作。如以下所示:

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)。

  至於有哪些不同的資料型態,實際上是根據不同程式語言而有所差異的。所以關於變數,剩下的內容就留待介紹各種程式語言時再做說明了。

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 指正。)


  而不論是編譯式語言、直譯式語言、還是混合式語言,都各擁有其優點與缺點。至於孰優孰劣,就只能視情況、見仁見智囉。

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)被轉換為適當的機器語言,使得程式不再只因轉移平台,就需要將程式全部重新撰寫的情形。




  自從高階語言出現以來,許許多多不同的程式概念與寫法不斷被提出、修正、改良。直到現今,仍然有許多代表著不同想法的高階語言不斷被創造出來。程式設計學術之盛,由此可見一斑。