---------------------------------------------------------------------------------------------------------------------------------------------------
Textbook:
C/C++程式設(shè)計範(fàn)例教本 課本範(fàn)例檔、
課本投影片、
課本習(xí)題解答 (pdf)、
課本勘誤表 (pdf)
課本提供之 DEV-C++ 用法課本提供之 C 語言標(biāo)準函式庫解說Primary Programming Paradigms (程式設(shè)計模式簡介)
基本結(jié)構(gòu)化程式設(shè)計簡介關(guān)於流程圖 (flowchart)Pseudo Code ExampleDev C++ IDE download
Dev C++ 用法C++ Operator Precedence (優(yōu)先順序; 優(yōu)先權(quán)) and Associativity (結(jié)合性)Escape Sequence作業(yè)繳交練習(xí)
--------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------
Additional Notes:
Source Program (原始程式) 也就是你所寫的應(yīng)用程式 (application) 的單一統(tǒng)稱。
一個 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」之後,就會產(chǎn)生 xxx.s 的組合語言程式檔。 請注意路徑的問題,可以的話,適當(dāng)?shù)脑谌魏螜n名之前,加入完整路徑。否則會產(chǎn)生無法找到檔案的錯誤。 你可以用記事本來打開此一組合語言程式檔,觀察由編譯器所生的組合語言程式是長怎樣。
若你要組譯一個組合語言程式檔,則也可利用 gcc.exe 來幫你組譯,請在 MS-DOS 模式下,鍵入「c:\dev-cpp\bin\gcc.exe -o myexe xxx.s」之後,就會產(chǎn)生 myexe.exe 的執(zhí)行檔 。 選項 -o myexe 是用來指定要產(chǎn)生的執(zhí)行檔的檔名用的。
雖說 gcc 是一個編譯器,但其實它是一個集各種系統(tǒng)程式於一身的程式。它含有 preprocessor、compiler、assembler、linker 等功能,但 compiler 還是它的主要角色。
在 C 中,
何謂一個識別字的壽命 (lifetime) ?何謂儲存空間類別 (storage class) ?External linkageInternal linkageNo linkage識別字的有效範(fàn)圍與其可視性 (Scope and Visibility)Lifetime 與 Visibility 的總結(jié)C/C++ 語言的所有前置處理指令 (Preprocessor Directives)#define 前置處理指令用法 EOF (end-of-file) 標(biāo)記: 在 C 語言中,EOF 是一個標(biāo)記用以代表一個檔案結(jié)束的情況,它是一個整數(shù)常數(shù)值,用前置處理指令#define EOF (-1) 來定義,且大部份編譯系統(tǒng)上 EOF 即代表 -1。 通常 EOF 被用在 I/O 系統(tǒng)函式呼叫上面,例如檔案讀取與鍵盤輸入上。當(dāng)鍵盤讀取上產(chǎn)生錯誤、或檔案讀取讀到了檔尾時,就會回傳 EOF 值。不過,若是用在鍵盤輸入上,有另外一種情況也會回傳 EOF 值,即從鍵盤上按下 Ctrl-z 時 (DOS 作業(yè)系統(tǒng))、或者是從鍵盤上按下 Ctrl-z 時 (UNIX 作業(yè)系統(tǒng))。
三維陣列範(fàn)例 遞迴函式 (recursive function) 解說
(階層: 以課本程式範(fàn)例 ch7-5-2.c 為例) |
(河內(nèi)塔: 以課本程式範(fàn)例 ch7-5-3.c 為例) |
(以費伯納西數(shù)列 (Fibonacci Numbers) 為例 ) 程式中任何需要「條件運算式」的地方 (註: 也就是需要其判斷結(jié)果為「真值」或「假值」的地方),其可為單一關(guān)係運算式,例如: (a >= 10),或者組合多個關(guān)係運算式,例如: ( (a > 5) || (b < 20) )。所以,你可以利用邏輯運算子「|| (也就是 OR)」及「&& (也就是 AND)」來組合成一個「邏輯運算式」,進而形成了一個具多條件判斷的「條件運算式」。它們最後的結(jié)果一定要為「真」或「假」。另再次強調(diào),在標(biāo)準 C 中,並沒有所謂「真 (true)」與「假 (false)」的資料值,取而代之的是,以數(shù)值 「0」代表「假值」,且以「非 0」的數(shù)值代表「真值」; 例如,以下所列出的數(shù)值若用以代表邏輯值的話,都會被看作「真值」:「123」、「-100」、「999」、「0.456」、「-9.123」等。但若要主動地請執(zhí)行中的程式告訴你什麼是真值的話,它會告訴你,就是「1」。 如何看待「 x++」 與「 ++x」 ? 請先觀察下列原始程式檔: (++ 與 -- 可應(yīng)用在整數(shù)、浮點數(shù)、或字元上) *若使用在字元上則代表其 ASCIl 碼遞增或遞減*
執(zhí)行結(jié)果:
基本上,x++ 與 ++x 若是自成一個敘述,則「++」擺在變數(shù) x 的前面或後面皆無妨 (一樣是變數(shù) x 內(nèi)容加 1 ),例如: 「x++;」、「++x;」。但是,若是 x++ 或 ++x 只是一個大敘述中的一小部份,例如上圖中的 11、13、15 行,此時,「++」的位置在前或在後會影響運算結(jié)果?!?+」的位置在前的,代表本身的動作先看 (先運算,即自己加 1 ),然後再看 (或再運算) 整個大敘述 (亦即指「被取值」); 相反的,若是「++」的位置在後的,代表整個大敘述先看 (即先運算,指「被取值」),然後再看 (即再運算) 本身的動作 (即自己加 1 )。所以,x++ 代表 x 先被取值,然後才 x 本身加 1; ++x 代表 x 本身加 1 ,然後 x 才被取值。當(dāng) x++ 或 ++x 在其所處的敘述中無關(guān)取不取值時,則 ++ 在前或在後皆無妨。
舉例來說,在上圖第 11 行中,因為 C 語言中早已規(guī)定了函式裡的參數(shù)傳遞順序為「由右至左」,故系統(tǒng)函式 printf() 中的第三個參數(shù)「++x」會先傳入給 printf(),然後第二個參數(shù)「x」再傳入給 printf(),最後才是字串「"%d, %d\n"」傳給 printf()。 但是因為第三個參數(shù)「++x」中,其「++」是放在變數(shù) x 的前面,因此在考慮整體大敘述的運算意義之前,變數(shù) x 本身要先運算 (即本身加 1),也就是說,此刻 x 已變?yōu)?11 了,並把 11 傳入給函式 printf()。再來,緊接著第 2 個參數(shù)準備要傳給系統(tǒng)函式 printf() 了,因為之前第 3 個參數(shù)在傳入 printf() 之前,x 已被加 1 ,故這時準備將第 2 個參數(shù)傳入時,是傳入 11 給 printf()。所以,最後印出來的結(jié)果是「11, 11」。
同樣地,在上圖第 13 行中,系統(tǒng)函式 printf() 中的第三個參數(shù)「y」會先傳入給 printf(),然後第二個參數(shù)「++y」再傳入給 printf(),最後才是「"%d, %d\n"」傳給 printf()。 第三個參數(shù)傳入時,「y」為 10。接著,第 2 個參數(shù)「++y」傳入時,因為「++」是放在變數(shù) y 的前面,因此在考慮整體大敘述的運算意義之前,變數(shù) y本身要先運算 (即本身加 1),也就是說,此刻 y 已變?yōu)?11 了,並把 11 傳入給函式 printf()。最後,才是第 3 個參數(shù)要傳給系統(tǒng)函式 printf()。所以,最後印出來的結(jié)果是「11, 10」。
再來,在上圖第 15 行「z = (++a) + (b++);」中,整個敘述的意義為將 (++a) + (b++) 的結(jié)果指定給變數(shù) z,但是,因為 (++a) 中的「++」放在變數(shù) a 的前面,所以,在考慮整體敘述運算之前,變數(shù) a 本身會先加 1。至於 (b++) 的部份,由於 (b++) 中的「++」放在變數(shù) b 的後面,所以,在整體敘述運算之後,變數(shù) b 本身才會加 1。綜合上述,所以變數(shù) z 的值為 21 + 30 = 51。
最後要注意的是,在一個指定運算式 (assignment expression) 中,「++」與「 --」運算子最好是只出現(xiàn)在等號右邊,如上圖第 15 行所示。若出現(xiàn)在等號左邊,則會顯得毫無意義 (等號 "=" 為指定運算子,其左方運算式結(jié)果一定要是左值 "Lvalue")。例如「++x = 100;」,這裡的變數(shù) x 將會永遠被指定成 100,即使 x 本身是先被加 1,但最後 x 還是被指定成 100。此外,「x++ = 100;」則為錯誤寫法。總而言之,應(yīng)避免在等號的左邊出現(xiàn) "++" 與 "--"。
何謂「左值 (left value; location value; L-value)」與「右值 (right value; R-value)」?
一個運算式 (expression) 是由一些連續(xù)的運算子 (operator) 與運算元 (operand) 所組成,並形成一個計算後的結(jié)果。一個指定運算式 (assignment expression) 有下列的格式: e1 = e2,其中 e1 和 e2 是運算式。指定運算子 "等號" 的右邊運算元 e2 可以是任何的運算式。但是,左邊的運算元 e1 則必需是 Lvalue 運算式,也就是說,它必需是一個可參考到某一個物件個體的運算式 (亦即可參考到一個用以儲存資料的記憶體位址)。這裡的 "L" 代表 "left",也就是指 "指定運算子的左邊"。 例:
int n;
宣告了一個型態(tài)為 int 的物件個體 (即變數(shù))。若:
n = 3;
其中 n 為一個運算式 (即整個指定運算式的一個子運算式) 並參考到了一個物件個體。在這裡運算式 n 為一個 Lvalue。另一方面,若:
3 = n;
這會產(chǎn)生一個編譯錯誤,因為它會試著去改變一個整數(shù)常數(shù) (這不合常理!)。雖然指定運算子的左邊運算元 3 是個運算式,但是它並不是一個左值 (它無法代表一個記憶體位置),事實上它是一個右值 (Rvalue)。簡單地說,右值是一個非左值的運算式,它並不會參考到任何一個物件個體 (記憶體位置),它只是單純代表一個資料值。
指定運算子並非唯一需要 Lvalue 的運算子,單元運算子 (unary operator) & (稱作 address-of 運算子) 也是需要一個左值來做為其唯一的運算元,亦即,唯有當(dāng) n 是 Lvalue 時,&n 才成為正確的寫法,而且其運算結(jié)果也是一個 Lvalue。因此,一個運算式,例如 &3,這是一個錯誤的寫法,因為常數(shù) 3 並沒有參考到任何一個物件個體 (記憶體位置),故它是無法被用來定址的。還有,二元運算子 (binary operator) "+" 的運算結(jié)果則會產(chǎn)生一個 Rvalue。 故:
m + 1 = n;
是一個錯誤寫法,運算子 "+" 擁有比運算子 "=" 還高的優(yōu)先順序 (precedence),因此這個運算式相當(dāng)於:
(m + 1) = n;
這也是個錯誤的寫法,因為 (m + 1) 的結(jié)果是一個 Rvalue。
另一方面,一個運算子也有可能接受 Rvalue 做為其運算元,而且產(chǎn)生一個 Lvalue 的運算結(jié)果。例如單元運算子 "*" 即是如此。
浮點數(shù) (floating-point; 即具有小數(shù)的數(shù)值) 使用上的一些注意事項:
浮點數(shù)根據(jù) IEEE 754 的標(biāo)準,具有 3 種儲存格式:
float precision 格式 (亦稱 single precision; 單精準度; 即 C 中的 float 資料型態(tài)): 利用 32 位元來儲存浮點數(shù)。 double precision 格式 (倍精準度; 即 C 中的 double 資料型態(tài)): 利用 64 位元來儲存浮點數(shù)。 double-extended precision 格式 (延伸倍精準度; 即 C 中的 long double 資料型態(tài)): 利用至少 80 位元來儲存浮點數(shù)。
下圖為 float precision 的儲存格式 (32 bits):
其中,bit 31 為符號位元 (sign bit),「0」代表正號,「1」代表負號。
bit 23 到 bit 30 為指數(shù)欄位 (exponent field),總共 8 個位元。這個欄位使用「excess-127碼」來儲存以 2 為底的指數(shù),也就是說,先將真正的指數(shù)再加上 127 之後,才會存入此欄位中。例如,若指數(shù)為 0,則將 0+127 = 127 (也就是二進位的 01111111) 存入此欄位中。下圖中列出了一些原始指數(shù)值經(jīng)過轉(zhuǎn)換變成了 excess-127 碼的例子。
bit 0 到 bit 22 為尾數(shù)欄位 (mantissa field; 亦稱為 significand field; 有效數(shù)欄位、或 fraction field; 小數(shù)欄位),總共 23 個位元。資料存入這個欄位之前,需經(jīng)過正規(guī)化的運算。
舉例來說,將十進位浮點數(shù) +2.25 存入 float precision 的浮點數(shù)格式時,會先將 +2.25 轉(zhuǎn)換成二進制的浮點數(shù)表示法,變成「+10.01」(見下圖)。
下圖列舉出一些二進制小數(shù)點之下,每一位位元所代表的十進位值之轉(zhuǎn)換:
然後將此表示法 「+10.01」予以正規(guī)化 (normalozation) 使變成小數(shù)點左方只有且必需有一個位元值「1」,例如,變成了「+1.001x 21」。此時,因為此數(shù)值為正數(shù),所以 sign bit 為「1」。然後,將「1.001」中小數(shù)點與其左方的位元值 1去除之後,剩下的「001」就是 mantissa 欄位值。最後,因為其為 2 的 1 次方,故以 2 為底的指數(shù)值為 1,但因為要用 excess-127 碼來存入,故要先將 1 加上 127 (= 128; 即 10000000),再將 128 (10000000) 存入 exponent 欄位。
經(jīng)由上述的運算之後,浮點數(shù) +2.25 以 float precision 格式 (單精準) 來儲存時的 32 位元內(nèi)容值即為:
0 10000000 00100000000000000000000
關(guān)於正規(guī)化的解說,請往下看:
其他的一些轉(zhuǎn)換成浮點數(shù)格式的例子,如下圖所示:
至於其他兩種浮點數(shù)格式的儲存方式,與上述 float precision 格式相似,但欄位長度不同,如下表所示: (其中的 bias (偏) 即為要額外加到真正指數(shù)的一個數(shù)值)
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 碼)
以下提供一些浮點數(shù)轉(zhuǎn)換工具的超連結(jié):
10 進位浮點數(shù) (Decimal Floating-Point Numbers) 轉(zhuǎn)換成 IEEE-754 32 位元與 64 位元 16 進位表示法IEEE-754 32 位元 16 進位表示法 (Hexadecimal Representation) 轉(zhuǎn)換成 10 進位浮點數(shù) (Decimal Floating-Point Numbers)IEEE-754 64 位元 16 進位表示法 (Hexadecimal Representation) 轉(zhuǎn)換成 10 進位浮點數(shù) (Decimal Floating-Point Numbers)二進制實數(shù)如何轉(zhuǎn)換成十進制實數(shù) ? 見下圖例子:
其他二進制小數(shù)轉(zhuǎn)換成十進制的一些例子:
但是在現(xiàn)實生活中,一般出現(xiàn)的十進位實數(shù)數(shù)值並不會如上表這麼單純 (例如: 單純的十進位實數(shù)如 5/16 就是二進位實數(shù)的 0.0101,它可以完全地被以二進位來表示)。但是,如同我們之前所提過的,有些十進位實數(shù)是無法正確地被轉(zhuǎn)換成二進位實數(shù)表示法的。以下,以十進位實數(shù)數(shù)值 0.2 (即 1/5) 為例,我們來看看它是如何被轉(zhuǎn)換成二進位實數(shù)表示法 (我們只看 mantissa 尾數(shù)的部份) :
下面提供一個
C 程式的例子,說明浮點數(shù)在使用上的一些注意事項:
執(zhí)行結(jié)果如下圖:
說明:
首先,在 A 部份 (第 5 行到第 11 行):
因為 1.25 以 float 來儲存於記憶體中,其 32 位元的內(nèi)容為:
0 01111111 01000000000000000000000
又 1.25 以 double 來儲存於記憶體中,其 64 位元的內(nèi)容為:
0 01111111111 0100000000000000000000000000000000000000000000000000
由此可知,1.25 剛好可以正確地被用二進位表示出來。因此,當(dāng)?shù)?8 行做 "相等" 的比較時,將會同時以 double 型態(tài)來做比較 (原為 float 型態(tài)的會被暫時轉(zhuǎn)換成 double 型態(tài)),因此當(dāng) 32 位元的 1.25 (float 型態(tài))被轉(zhuǎn)成 64 位元的 double 型態(tài)時,變成了:
0 01111111111 0100000000000000000000000000000000000000000000000000
其中,在上一行裡,紅色部份為轉(zhuǎn)換型態(tài)之後 (被擴大了),被補充上去的資料。因此,比較完之後,比較兩邊雙方確實完全相等,因此印出了「1」(真值)。
再來,我們看 B 部份 (第 13 行到第 19 行):
因為 1.27 以 float 來儲存於記憶體中,其 32 位元的內(nèi)容為:
0 01111111 01000101000111101011100
又 1.27 以 double 來儲存於記憶體中,其 64 位元的內(nèi)容為:
0 01111111111 0100010100011110101110000101000111101011100001010010
由此可知,1.27 無法被正確地以二進位來完整地表示出來 (上面的 float 格式的 mantissa 部份由於只能表示 23 個位元,但事實上,後面還有很多位元無法被表現(xiàn)出來)。因此,當(dāng) 1.27 被以 float 的格式儲存到記憶體時,它是取最接近 1.27 的數(shù)值來儲存 (大約是 1.2699999809265137); 相同的情況也發(fā)生在 double 格式時,上圖的 d2 變數(shù)的執(zhí)行結(jié)果印出了 1.270000000000000000..... 的原因乃是因為 printf() 在輸出時會自動為浮點數(shù)做必要的進位,事實上 d2 並不剛好等於 1.27。當(dāng)?shù)?16 行做 "相等" 的比較時,也是一樣,將會同時以 double 型態(tài)來做比較 (原為 float 型態(tài)的也會被暫時地轉(zhuǎn)換成 double 型態(tài)),因此當(dāng) 32 位元的 1.27 (float 型態(tài))被轉(zhuǎn)成 64 位元的 double 型態(tài)時,變成了:
0 01111111111 0100010100011110101110000000000000000000000000000000
其中,在上一行裡,紅色部份為轉(zhuǎn)換型態(tài)之後 (被擴大了),被補充上去的資料。因此,比較完之後,比較兩邊雙方確實不相等,因此印出了「0」假值)。
最後,我們看 C 部份 (第 21 行到第 29 行):
因為 1.2700001 (即變數(shù) a) 以 float 來儲存於記憶體中,其 32 位元的內(nèi)容為: (rounded; 即進位後結(jié)果)
0 01111111 01000101000111101011101
又 1.27000001(即變數(shù) b) 以 float 來儲存於記憶體中,其 32 位元的內(nèi)容為: (rounded; 即進位後結(jié)果)
0 01111111 01000101000111101011100
而 1.27 (即變數(shù) c) 以 float 來儲存於記憶體中,其 32 位元的內(nèi)容為: (rounded; 即進位後結(jié)果)
0 01111111 01000101000111101011100
由此可知,1.2700001 與 1.27000001 也是一樣無法被正確地以二進位來完整地表示出來 (上面的 float 格式的 mantissa 部份由於只能表示 23 個位元,但事實上,後面還有很多位元無法被表現(xiàn)出來)。因此,當(dāng) 1.2700001 被以 float 的格式儲存到記憶體時,它是取最接近 1.2700001 的數(shù)值來儲存 (大約是 1.2700001001358032); 相同的情況也發(fā)生在 1.27000001 的 float 格式時。當(dāng)?shù)?25 行做 "相等" 的比較時,由上面所列出來的二進位表示法可以得知,變數(shù) a 與變數(shù) c 確實不相等,因此印出了「0」假值)。同時,當(dāng)?shù)?26 行做 "相等" 的比較時,變數(shù) b 與變數(shù) c 確實相等,因此印出了「1」這個真值)。
如此一來,你可以看出來為何 (1.2700001 != 1.27) 但是 (1.27000001 == 1.27) 的原因所在了。因此,在相當(dāng)注重浮點數(shù)運算的應(yīng)用程式中,要特別小心使用浮點數(shù)。