資工三甲 計算機程式設計

来源:百度文库 编辑:神马文学网 时间:2024/05/02 02:16:48
---------------------------------------------------------------------------------------------------------------------------------------------------
Textbook:C/C++程式設計範例教本
課本範例檔、課本投影片、課本習題解答 (pdf)、課本勘誤表 (pdf)
課本提供之 DEV-C++ 用法
課本提供之 C 語言標準函式庫解說
Primary Programming Paradigms  (程式設計模式簡介)
基本結構化程式設計簡介
關於流程圖 (flowchart)
Pseudo Code Example
Dev C++ IDE downloadDev C++ 用法
C++ Operator Precedence (優先順序; 優先權) and Associativity (結合性)
Escape Sequence
作業繳交練習
--------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------
Additional Notes:
Source Program (原始程式) 也就是你所寫的應用程式 (application) 的單一統稱。
一個 Source Program可能含有很多 (但至少一個) Source Files (原始程式檔) 所謂 Source File 就是一個 xxx.c 或 xxx.cpp (若是 Java 則為 xxx.java) 的原始程式檔,Source File 中的程式碼被稱為 source codes (原始程式碼; 源碼)。 一個 Source File 也可被稱作 translation unit (翻譯單元) 或 compilation unit (編譯單元)。也就是說,Source File 是編譯器 (C/C++ compiler或 Java compiler) 可處理的最小編譯單元。
如何將 C 的原始程式檔編譯成組合語言程式碼 (assembly codes) ? 請在 MS-DOS 模式下,鍵入「c:\dev-cpp\bin\gcc.exe -S xxx.c」之後,就會產生 xxx.s 的組合語言程式檔。 請注意路徑的問題,可以的話,適當的在任何檔名之前,加入完整路徑。否則會產生無法找到檔案的錯誤。 你可以用記事本來打開此一組合語言程式檔,觀察由編譯器所生的組合語言程式是長怎樣。
若你要組譯一個組合語言程式檔,則也可利用 gcc.exe 來幫你組譯,請在 MS-DOS 模式下,鍵入「c:\dev-cpp\bin\gcc.exe -o myexe xxx.s」之後,就會產生 myexe.exe 的執行檔 。 選項 -o myexe 是用來指定要產生的執行檔的檔名用的。
雖說 gcc 是一個編譯器,但其實它是一個集各種系統程式於一身的程式。它含有 preprocessor、compiler、assembler、linker 等功能,但 compiler 還是它的主要角色。
在 C 中,何謂一個識別字的壽命 (lifetime) ?何謂儲存空間類別 (storage class) ?External linkageInternal linkageNo linkage
識別字的有效範圍與其可視性 (Scope and Visibility)Lifetime 與 Visibility 的總結
C/C++ 語言的所有前置處理指令 (Preprocessor Directives)#define 前置處理指令用法 EOF (end-of-file) 標記: 在 C 語言中,EOF 是一個標記用以代表一個檔案結束的情況,它是一個整數常數值,用前置處理指令#define EOF (-1) 來定義,且大部份編譯系統上 EOF 即代表 -1。 通常 EOF 被用在 I/O 系統函式呼叫上面,例如檔案讀取與鍵盤輸入上。當鍵盤讀取上產生錯誤、或檔案讀取讀到了檔尾時,就會回傳 EOF 值。不過,若是用在鍵盤輸入上,有另外一種情況也會回傳 EOF 值,即從鍵盤上按下 Ctrl-z 時 (DOS 作業系統)、或者是從鍵盤上按下 Ctrl-z 時 (UNIX 作業系統)。三維陣列範例 遞迴函式 (recursive function) 解說 (階層: 以課本程式範例 ch7-5-2.c 為例) |(河內塔: 以課本程式範例 ch7-5-3.c 為例) |(以費伯納西數列 (Fibonacci Numbers) 為例 ) 程式中任何需要「條件運算式」的地方 (註: 也就是需要其判斷結果為「真值」或「假值」的地方),其可為單一關係運算式,例如: (a >= 10),或者組合多個關係運算式,例如: ( (a > 5) || (b < 20) )。所以,你可以利用邏輯運算子「|| (也就是 OR)」及「&& (也就是 AND)」來組合成一個「邏輯運算式」,進而形成了一個具多條件判斷的「條件運算式」。它們最後的結果一定要為「真」或「假」。另再次強調,在標準 C 中,並沒有所謂「真 (true)」與「假 (false)」的資料值,取而代之的是,以數值 「0」代表「假值」,且以「非 0」的數值代表「真值」; 例如,以下所列出的數值若用以代表邏輯值的話,都會被看作「真值」:「123」、「-100」、「999」、「0.456」、「-9.123」等。但若要主動地請執行中的程式告訴你什麼是真值的話,它會告訴你,就是「1」。 如何看待「 x++」 與「 ++x」 ? 請先觀察下列原始程式檔: (++ 與 -- 可應用在整數、浮點數、或字元上) *若使用在字元上則代表其 ASCIl 碼遞增或遞減*

執行結果:
基本上,x++ 與 ++x 若是自成一個敘述,則「++」擺在變數 x 的前面或後面皆無妨 (一樣是變數 x 內容加 1 ),例如: 「x++;」、「++x;」。但是,若是 x++ 或 ++x 只是一個大敘述中的一小部份,例如上圖中的 11、13、15 行,此時,「++」的位置在前或在後會影響運算結果。「++」的位置在前的,代表本身的動作先看 (先運算,即自己加 1 ),然後再看 (或再運算) 整個大敘述 (亦即指「被取值」); 相反的,若是「++」的位置在後的,代表整個大敘述先看 (即先運算,指「被取值」),然後再看 (即再運算) 本身的動作 (即自己加 1 )。所以,x++ 代表 x 先被取值,然後才 x 本身加 1; ++x 代表 x 本身加 1 ,然後 x 才被取值。當 x++ 或 ++x 在其所處的敘述中無關取不取值時,則 ++ 在前或在後皆無妨。
舉例來說,在上圖第 11 行中,因為 C 語言中早已規定了函式裡的參數傳遞順序為「由右至左」,故系統函式 printf() 中的第三個參數「++x」會先傳入給 printf(),然後第二個參數「x」再傳入給 printf(),最後才是字串「"%d, %d\n"」傳給 printf()。 但是因為第三個參數「++x」中,其「++」是放在變數 x 的前面,因此在考慮整體大敘述的運算意義之前,變數 x 本身要先運算 (即本身加 1),也就是說,此刻 x 已變為 11 了,並把 11 傳入給函式 printf()。再來,緊接著第 2 個參數準備要傳給系統函式 printf() 了,因為之前第 3 個參數在傳入 printf() 之前,x 已被加 1 ,故這時準備將第 2 個參數傳入時,是傳入 11 給 printf()。所以,最後印出來的結果是「11, 11」。
同樣地,在上圖第 13 行中,系統函式 printf() 中的第三個參數「y」會先傳入給 printf(),然後第二個參數「++y」再傳入給 printf(),最後才是「"%d, %d\n"」傳給 printf()。 第三個參數傳入時,「y」為 10。接著,第 2 個參數「++y」傳入時,因為「++」是放在變數 y 的前面,因此在考慮整體大敘述的運算意義之前,變數 y本身要先運算 (即本身加 1),也就是說,此刻 y 已變為 11 了,並把 11 傳入給函式 printf()。最後,才是第 3 個參數要傳給系統函式 printf()。所以,最後印出來的結果是「11, 10」。
再來,在上圖第 15 行「z = (++a) + (b++);」中,整個敘述的意義為將 (++a) + (b++) 的結果指定給變數 z,但是,因為 (++a) 中的「++」放在變數 a 的前面,所以,在考慮整體敘述運算之前,變數 a 本身會先加 1。至於 (b++) 的部份,由於 (b++) 中的「++」放在變數 b 的後面,所以,在整體敘述運算之後,變數 b 本身才會加 1。綜合上述,所以變數 z 的值為 21 + 30 = 51。
最後要注意的是,在一個指定運算式 (assignment expression) 中,「++」與「 --」運算子最好是只出現在等號右邊,如上圖第 15 行所示。若出現在等號左邊,則會顯得毫無意義 (等號 "=" 為指定運算子,其左方運算式結果一定要是左值 "Lvalue")。例如「++x = 100;」,這裡的變數 x 將會永遠被指定成 100,即使 x 本身是先被加 1,但最後 x 還是被指定成 100。此外,「x++ = 100;」則為錯誤寫法。總而言之,應避免在等號的左邊出現 "++" 與 "--"。
何謂「左值 (left value; location value; L-value)」與「右值 (right value; R-value)」?
一個運算式 (expression) 是由一些連續的運算子 (operator) 與運算元 (operand) 所組成,並形成一個計算後的結果。一個指定運算式 (assignment expression) 有下列的格式: e1 = e2,其中 e1 和 e2 是運算式。指定運算子 "等號" 的右邊運算元 e2 可以是任何的運算式。但是,左邊的運算元 e1 則必需是 Lvalue 運算式,也就是說,它必需是一個可參考到某一個物件個體的運算式 (亦即可參考到一個用以儲存資料的記憶體位址)。這裡的 "L" 代表 "left",也就是指 "指定運算子的左邊"。 例:
int n;
宣告了一個型態為 int 的物件個體 (即變數)。若:
n = 3;
其中 n 為一個運算式 (即整個指定運算式的一個子運算式) 並參考到了一個物件個體。在這裡運算式 n 為一個 Lvalue。另一方面,若:
3 = n;
這會產生一個編譯錯誤,因為它會試著去改變一個整數常數 (這不合常理!)。雖然指定運算子的左邊運算元 3 是個運算式,但是它並不是一個左值 (它無法代表一個記憶體位置),事實上它是一個右值 (Rvalue)。簡單地說,右值是一個非左值的運算式,它並不會參考到任何一個物件個體 (記憶體位置),它只是單純代表一個資料值。
指定運算子並非唯一需要 Lvalue 的運算子,單元運算子 (unary operator) & (稱作 address-of 運算子) 也是需要一個左值來做為其唯一的運算元,亦即,唯有當 n 是 Lvalue 時,&n 才成為正確的寫法,而且其運算結果也是一個 Lvalue。因此,一個運算式,例如 &3,這是一個錯誤的寫法,因為常數 3 並沒有參考到任何一個物件個體 (記憶體位置),故它是無法被用來定址的。還有,二元運算子 (binary operator) "+" 的運算結果則會產生一個 Rvalue。 故:
m + 1 = n;
是一個錯誤寫法,運算子 "+" 擁有比運算子 "=" 還高的優先順序 (precedence),因此這個運算式相當於:
(m + 1) = n;
這也是個錯誤的寫法,因為 (m + 1) 的結果是一個 Rvalue。
另一方面,一個運算子也有可能接受 Rvalue 做為其運算元,而且產生一個 Lvalue 的運算結果。例如單元運算子 "*" 即是如此。
浮點數 (floating-point; 即具有小數的數值) 使用上的一些注意事項:
浮點數根據 IEEE 754 的標準,具有 3 種儲存格式:
float precision 格式 (亦稱 single precision; 單精準度; 即 C 中的 float 資料型態): 利用 32 位元來儲存浮點數。 double precision 格式 (倍精準度; 即 C 中的 double 資料型態): 利用 64 位元來儲存浮點數。 double-extended precision 格式 (延伸倍精準度; 即 C 中的 long double 資料型態): 利用至少 80 位元來儲存浮點數。
下圖為 float precision 的儲存格式 (32 bits):

其中,bit 31 為符號位元 (sign bit),「0」代表正號,「1」代表負號。
bit 23 到 bit 30 為指數欄位 (exponent field),總共 8 個位元。這個欄位使用「excess-127碼」來儲存以 2 為底的指數,也就是說,先將真正的指數再加上 127 之後,才會存入此欄位中。例如,若指數為 0,則將 0+127 = 127 (也就是二進位的 01111111) 存入此欄位中。下圖中列出了一些原始指數值經過轉換變成了 excess-127 碼的例子。

bit 0 到 bit 22 為尾數欄位 (mantissa field; 亦稱為 significand field; 有效數欄位、或 fraction field; 小數欄位),總共 23 個位元。資料存入這個欄位之前,需經過正規化的運算。
舉例來說,將十進位浮點數 +2.25 存入 float precision 的浮點數格式時,會先將 +2.25 轉換成二進制的浮點數表示法,變成「+10.01」(見下圖)。

下圖列舉出一些二進制小數點之下,每一位位元所代表的十進位值之轉換:

然後將此表示法 「+10.01」予以正規化 (normalozation) 使變成小數點左方只有且必需有一個位元值「1」,例如,變成了「+1.001x 21」。此時,因為此數值為正數,所以 sign bit 為「1」。然後,將「1.001」中小數點與其左方的位元值 1去除之後,剩下的「001」就是 mantissa 欄位值。最後,因為其為 2 的 1 次方,故以 2 為底的指數值為 1,但因為要用 excess-127 碼來存入,故要先將 1 加上 127 (= 128; 即 10000000),再將 128 (10000000) 存入 exponent 欄位。
經由上述的運算之後,浮點數 +2.25 以 float precision 格式 (單精準) 來儲存時的 32 位元內容值即為:
0 10000000 00100000000000000000000
關於正規化的解說,請往下看:

其他的一些轉換成浮點數格式的例子,如下圖所示:

至於其他兩種浮點數格式的儲存方式,與上述 float precision 格式相似,但欄位長度不同,如下表所示: (其中的 bias (偏) 即為要額外加到真正指數的一個數值)
floating-point format Sign field Exponent field Fraction field Bias
Single Precision
1 [bit 31] 8 [bit 30 到 bit 23] 23 [bit 22 到 bit 00] 127 (excess-127 碼)
Double Precision
1 [bit 63] 11 [bit 62 到 bit 52] 52 [bit 51 到 bit 00] 1023 (excess-1023 碼)
Double-extended Precision
1 [bit 79] 15 [bit 78 到 bit 64] 64 [bit 63 到 bit 00] 16383 (excess-16383 碼)
以下提供一些浮點數轉換工具的超連結:10 進位浮點數 (Decimal Floating-Point Numbers) 轉換成 IEEE-754 32 位元與 64 位元 16 進位表示法IEEE-754 32 位元 16 進位表示法 (Hexadecimal Representation) 轉換成 10 進位浮點數 (Decimal Floating-Point Numbers)IEEE-754 64 位元 16 進位表示法 (Hexadecimal Representation) 轉換成 10 進位浮點數 (Decimal Floating-Point Numbers)
二進制實數如何轉換成十進制實數 ? 見下圖例子:

其他二進制小數轉換成十進制的一些例子:

但是在現實生活中,一般出現的十進位實數數值並不會如上表這麼單純 (例如: 單純的十進位實數如 5/16 就是二進位實數的 0.0101,它可以完全地被以二進位來表示)。但是,如同我們之前所提過的,有些十進位實數是無法正確地被轉換成二進位實數表示法的。以下,以十進位實數數值 0.2 (即 1/5) 為例,我們來看看它是如何被轉換成二進位實數表示法 (我們只看 mantissa 尾數的部份) :

下面提供一個 C 程式的例子,說明浮點數在使用上的一些注意事項:

執行結果如下圖:

說明:
首先,在 A 部份 (第 5 行到第 11 行):
因為 1.25 以 float 來儲存於記憶體中,其 32 位元的內容為:
0 01111111 01000000000000000000000
又 1.25 以 double 來儲存於記憶體中,其 64 位元的內容為:
0 01111111111 0100000000000000000000000000000000000000000000000000
由此可知,1.25 剛好可以正確地被用二進位表示出來。因此,當第 8 行做 "相等" 的比較時,將會同時以 double 型態來做比較 (原為 float 型態的會被暫時轉換成 double 型態),因此當 32 位元的 1.25 (float 型態)被轉成 64 位元的 double 型態時,變成了:
0 01111111111 0100000000000000000000000000000000000000000000000000
其中,在上一行裡,紅色部份為轉換型態之後 (被擴大了),被補充上去的資料。因此,比較完之後,比較兩邊雙方確實完全相等,因此印出了「1」(真值)。
再來,我們看 B 部份 (第 13 行到第 19 行):
因為 1.27 以 float 來儲存於記憶體中,其 32 位元的內容為:
0 01111111 01000101000111101011100
又 1.27 以 double 來儲存於記憶體中,其 64 位元的內容為:
0 01111111111 0100010100011110101110000101000111101011100001010010
由此可知,1.27 無法被正確地以二進位來完整地表示出來 (上面的 float 格式的 mantissa 部份由於只能表示 23 個位元,但事實上,後面還有很多位元無法被表現出來)。因此,當 1.27 被以 float 的格式儲存到記憶體時,它是取最接近 1.27 的數值來儲存 (大約是 1.2699999809265137); 相同的情況也發生在 double 格式時,上圖的 d2 變數的執行結果印出了 1.270000000000000000..... 的原因乃是因為 printf() 在輸出時會自動為浮點數做必要的進位,事實上 d2 並不剛好等於 1.27。當第 16 行做 "相等" 的比較時,也是一樣,將會同時以 double 型態來做比較 (原為 float 型態的也會被暫時地轉換成 double 型態),因此當 32 位元的 1.27 (float 型態)被轉成 64 位元的 double 型態時,變成了:
0 01111111111 0100010100011110101110000000000000000000000000000000
其中,在上一行裡,紅色部份為轉換型態之後 (被擴大了),被補充上去的資料。因此,比較完之後,比較兩邊雙方確實不相等,因此印出了「0」假值)。
最後,我們看 C 部份 (第 21 行到第 29 行):
因為 1.2700001 (即變數 a) 以 float 來儲存於記憶體中,其 32 位元的內容為: (rounded; 即進位後結果)
0 01111111 01000101000111101011101
又 1.27000001(即變數 b) 以 float 來儲存於記憶體中,其 32 位元的內容為: (rounded; 即進位後結果)
0 01111111 01000101000111101011100
而 1.27 (即變數 c) 以 float 來儲存於記憶體中,其 32 位元的內容為: (rounded; 即進位後結果)
0 01111111 01000101000111101011100
由此可知,1.2700001 與 1.27000001 也是一樣無法被正確地以二進位來完整地表示出來 (上面的 float 格式的 mantissa 部份由於只能表示 23 個位元,但事實上,後面還有很多位元無法被表現出來)。因此,當 1.2700001 被以 float 的格式儲存到記憶體時,它是取最接近 1.2700001 的數值來儲存 (大約是 1.2700001001358032); 相同的情況也發生在 1.27000001 的 float 格式時。當第 25 行做 "相等" 的比較時,由上面所列出來的二進位表示法可以得知,變數 a 與變數 c 確實不相等,因此印出了「0」假值)。同時,當第 26 行做 "相等" 的比較時,變數 b 與變數 c 確實相等,因此印出了「1」這個真值)。
如此一來,你可以看出來為何 (1.2700001 != 1.27) 但是 (1.27000001 == 1.27) 的原因所在了。因此,在相當注重浮點數運算的應用程式中,要特別小心使用浮點數。