llaanngguuaaggeepprrooggrraammmmiinnggweb.cjcu.edu.tw/~wowakai/c/deitel.pdf ·...

84
-1- L L a a n n g g u u a a g g e e P P r r o o g g r r a a m mm mi i n n g g About Textbook C 程式設計藝術/C: How to Program, 4/e, H. M. Deitel and P. J. Deitel 原著, 吳國樑編譯, ISBN 957-21-4427-8, 全華圖書, 2004. 關於作者─ Harvey J. Deitel Paul J. Deitel Deitel & Associates Inc.公司的總裁與執行總裁,該公司 公司是一家專門提供企業訓練課程與開發教材的國際性公司。 本書適用對象─ 1) 適合稍微有程式設計經驗或是完全沒有程式設計經驗的人。

Upload: hoangnhu

Post on 07-Feb-2018

230 views

Category:

Documents


8 download

TRANSCRIPT

- 1 -

程程程式式式設設設計計計LLLaaannnggguuuaaagggeee PPPrrrooogggrrraaammmmmmiiinnnggg

About Textbook

‧ C 程式設計藝術/C: How to Program, 4/e, H. M. Deitel and P. J. Deitel原著, 吳國樑編譯, ISBN 957-21-4427-8, 全華圖書, 2004.

‧ 關於作者─

Harvey J. Deitel 與 Paul J. Deitel 是 Deitel & Associates Inc.公司的總裁與執行總裁,該公司公司是一家專門提供企業訓練課程與開發教材的國際性公司。

‧ 本書適用對象─

1) 適合稍微有程式設計經驗或是完全沒有程式設計經驗的人。

- 2 -

2) 適合熟練且想要深入探討 C 語言的程式設計師。

‧ 學習箴言─「沒有一位程式設計師能在一開始就以正確的方法學習程式設計。」

‧ 本書分成四個主要部分─

1) C 語言:第 1 章到第 14 章 (程式設計 3 學分;大一上學期必修)

2) C++語言:第 15 章到第 23 章 (高等程式設計 3 學分;大二上學期選修)

3) Java 語言:第 24 章到第 30 章

2) 參考資料:附錄 A 到 F

第第第 111 章章章 CCC 語語語言言言簡簡簡介介介

本章目標

‧瞭解電腦組成的基本概念

‧能對不同類型的程式語言有一些認識

‧知道 C 程式語言的演進史

‧介紹 C 語言的標準函式庫

‧了解 C 程式的開發程序

1.1 電腦是什麼

‧ 矽是沙子的主要成分,也是地球上蘊藏量最豐富的元素之一。矽晶片技術讓電腦的製造與

使用成本越來越便宜,目前全球每年生產製造一億台以上的電腦,幫助人們處理商業、工

業、政府的各種業務,也協助處理個人生活上的許多事務。

‧ 電腦是能夠處理計算(加減乘除)與演算(邏輯判斷)的設備。

電腦功能的實現,相當於一種由無數個開關(電晶體;transistor)所進行的計畫性活動。

電腦一秒鐘就能夠完成的加法運算,人就是花上一輩子的時間也做不完。

目前個人電腦 CPU 的工作頻率以達 3.5Giga Hz, 而速度最快的超級電腦(supercomputers)又比這個快上 1 萬倍。

‧ 電腦系統包含硬體(hardware)、軟體(software)以及所處理的資料(data)。鍵盤、螢幕、滑鼠、

磁碟機、記憶體、DVD、CD-ROM、CPU 等都是硬體。而在電腦上執行的程式則稱為軟

體。

- 3 -

‧ 近年來硬體的價格大幅下滑,使得個人電腦成為大眾化商品,我們對軟體的需求日益提

高。不幸的是,軟體的撰寫方式並沒有大幅度的改變,以致於軟體的開發成本不斷上升。

‧ 學習程式設計就是在學習發展軟體的方法,良好的程式設計技術可以大幅降低軟體開發的

成本。

‧ 不管電腦的外觀如何不同,一部運作中的電腦,至少包含以下 6 大單元:

1. 輸入單元(Input Unit):接收區域

2. 輸出單元(Output Unit):送出區域

3. 記憶單元(Memory Unit):速度快、容量小、具揮發性

4. 算術及邏輯單元(Arithmetic and Logic Unit):運算區域

5. 中央處理單元(Central Processing Unit; CPU):管理區域

6. 儲存單元(Storage Unit):速度慢、容量大、不具揮發性

1.2 作業系統的演進

‧ 早期的電腦一次只能處理一件工作,例如 DOS 作業系統(operating system; OS),這種運作

方式的電腦稱之為單一使用者批次處理(batch processing)。

‧ 後來的電腦發展出可同時處理多個工作的作業系統,以提升電腦的使用效率,這就是所謂

的多工處理(multi-task processing)。

‧ 到了 1960 年代,工業界和學術界開始研究時間共享(time-sharing)作業系統,該系統可允

許數十或數百使用者同時使用電腦。事實上,電腦並不是真的同時為每個使用者處理工

作,而輪流地執行每個使用者的一小部分工作。因為電腦的速度很快,一秒內就可以處理

許多次每個使用者的工作,所以使用者感覺大家的工作,是在同一時間處理的。

‧ 起初,電腦是體積龐大、價格昂貴的大傢伙,直到 1977 年頻果電腦公司開始推廣個人電

腦後,隨著電腦價格的下降,人們才逐漸有能力購買電腦。目前的個人電腦,其性能已不

輸給 10-15 年前價值上億元的迷你超級電腦(mini-supercomputer)。

‧ 三種不同的計算環境

1. 單機式(personal):每個人在自己的電腦上做自己的事。

2. 分散式(distributed):把工作分送到不同的電腦上執行,完成後再收集起來處理。

3. 主從式(client/server):將電腦分成伺服器(server)與客戶端(client),客戶端負責主要的計

算工作,伺服器則負責提供所需資料之存取。

‧ 現今,C 和 C++已經成為撰寫各式應用軟體與作業系統的主要語言之一。

- 4 -

1.3 程式語言的類型

‧ 電腦語言有數百種,每一種可能還有許多版本。有些程式語言是電腦可以直接看得懂的,

有些則必須經過編譯才能看懂。基本上,這些程式語言可以分成三種類型:

1. 機器語言(Machine Language)。電腦可直接了解的語言

。機器語言是電腦的「自然語言」

。機器語言與電腦硬體的設計有密切的關係

。機器語言通常由一連串的 0、1 數字所組成,它會命令電腦一次執行一個基本的動作

。機器語言隨電腦的種類而異,即某種特殊的機器語言只適用於某種電腦

。機器語言對人類來說是很難閱讀和了解的

例如:下面的機器語言可將超時工資與基本工資相加,並計算出實領薪資

+1300042774+1400593419+1200274027

2. 組合語言(Assembly Language)。用類似英文縮寫的方式來取代機器語言的語言

。組合語言與機器語言合稱低階語言(Low-Level Language)。組合語言必須先經由轉換程式轉換成機器語言,才能在電腦上執行

。將組合語言翻譯成機械語言的翻譯器就叫做組譯器(assembler)。組譯器就是組合語言的翻譯器

例如:前面的例子相當於以下指令(instructions)

LOAD BASEPAYADD OVERPAYSTORE GROSSPAY

3. 高階語言(High-Level Language)。為加快程式撰寫的速度,高階語言便因應而生

。高階語言的一個敘述式就可以包含大量的工作

。用高階語言可以寫出類似日常英文用語的指令

。高階語言使用了一般常用的數學運算符號

。將高階語言翻譯成機械語言的翻譯器就叫做編譯器(compiler)。編譯器就是高階語言的翻譯器

例如:前面的例子用高階語言只需一個敘述

grossPay = basePay + overPay

‧ 程式語言的三種翻譯器─

1. 組譯器(assembler)2. 編譯器(compiler):將高階語言翻譯成機器語言並儲存成檔案,供日後執行用。

- 5 -

3. 直譯器(interpreter):將高階語言逐條翻譯成機器語言並直接執行,不儲存成檔案。

1.4 程式語言的演進

‧ 主要程式語言演進圖─

‧ FORTRAN 語言:FORTRAN (FORmula TRANslator)語言是 1950 年代由 IBM 所發展,主要是針對需要用到

複雜數學運算的科學及工程方面之應用。FORTRAN 現在仍被廣泛地應用在需要大量計算

的工程科學(機械、土木、航空等)領域。

‧ COBOL 語言:COBOL (COmmon Business Oriented Language)語言是 1959 年由電腦製造商、政府部門和

工業電腦使用者共同發展出來。COBOL 主要用來當作商業用途,方便精確而有效率地處

理大量的商業資料。目前有非常大比例的商業軟體仍然使用 COBOL 來撰寫。

‧ BASIC 語言:

- 6 -

1. BASIC (Beginner’s All-Purpose Sybolic Instruction Code)語言是 1960 年代由 Dartmouth學院的 John Kemeny 和 Thomas Kurtz 教授發展的,用來撰寫簡單的程式。BASIC 語言

的基本目的是使新手熟悉程式設計的技術。

2. Visual Basic 語言是 BASIC 語言的視窗可視化版本,它在 1991 年由微軟公司採用來發

展 Windows 應用程式。

‧ 結構化程式設計:

1. 在 1960 年代期間,許多大型軟體的開發都碰到嚴酷的考驗,研發常常超過時程,費用

大增,而軟體的可靠度卻不佳。人們了解到軟體的開發遠比他們想像的還要複雜,因

此出現了一種革命性的程式設計概念 ─ 結構化程式設計(Structured Programming)2. 「結構化程式設計概念」指的是「一種井然有序的程式撰寫風格」,可使所寫的程式更

清晰、更易於測試、除錯(debug)及修改。

‧ PASCAL 語言:1. 在 1971 年由 Niklaus Wirth 教授所發展,而以 17 世紀數學暨哲學家 Blaise Pascal 的名

字來命名。

2. Pascal 語言乃專為結構化程式設計的概念來設計,並且快速地成為大多數學院偏好的

程式語言。

3. 可惜的是,Pascal 語言因發展較晚,缺乏許多已經開發完成的原始程式可供使用,因

此最後沒有廣泛地被接受。

1.5 C 語言

‧ C 語言是在 1972 年由美國貝爾實驗室的 Dennis Ritchie 由 B 語言所發展出來的,而 B 語

言之前被用來發展 UNIX 作業系統的雛型。

‧ C 語言受到眾人注目是因為它是發展 UNIX 作業系統的語言。

‧ 今日,幾乎所有新的、主要的作業系統均是用 C 或 C++語言撰寫的。

‧ C 語言具有良好的可攜性(portability),C 語言幾乎與電腦硬體無關,只要經過小心的設計,

C 程式可適用於大多數的電腦。接著,C 語言在各種電腦(或稱硬體平台)上快速的發展,

並且產生了許多的版本。「何謂可攜性? ─ 幾乎可以不經修改便能拿到不同的電腦系統上執行。」

「影響程式可攜性的因素:1.程式語言本身可攜性之高低 2.個人程式設計技術之好壞」

‧ C 語言到了 1970 年代末期,發展成我們現在所謂的「傳統 C 語言」,確立了一些共同的規

則和語法。1983 年,美國國家標準局(ANSI)定義了一個 C 的標準版本,即 ANSI C。

- 7 -

1.6 C 標準函式庫

‧ C 的程式是由許多稱為函式的模組或片段所構成,程式設計師可以設計自己的函式,不過

大部分還是利用一套現成的函式,稱為「C 標準函式庫」(C Standard Library)。

‧ 學習 C 語言可分成兩個部分,一是學習 C 語言本身,二是學習如何使用 C 標準函式庫所

提供的函式。

‧ 一個 C 的程式是由 1)自己撰寫的函式 2)他人撰寫的函式 3)C 的標準函式庫所提供的函

式 所組成。

‧ 自己製作函式的好處是可以精確地掌握這個函式,缺點是必須花時間設計和檢驗。利用現

成的函式可以省卻一些麻煩,因為 ANSI 標準函式都是經過專家精心設計,而且可攜性較

高,所以容易適用於各種不同的電腦系統上。

1.7 C++語言

‧ C++也是由美國貝爾實驗室所發展出來的,C++包含了 C 語言的全部,而且增加了許多功

能使得 C 語言更加的完整。最重要的是 C++提供了物件導向式程式設計(Object-OrientedProgramming)的功能,使 C++已經成為工業界和學術界慣用的程式語言。

‧ 物件(Object)是模擬真實世界的物體或事件所形成的可重複使用的軟體元件。任何名詞都

可以表示成物件,我們可以產生諸如日期物件、時間物件、薪資物件、收據物件、聲音物

件、影像物件檔案物件等等。

‧ 在物件導向式程式語言出現之前,所有的程式語言(FORTRAN, Pascal, Basic 和 C 等)都將

注意力集中在動作(動詞)上,而非物件(名詞)上。軟體發展者發現用模組化以及物件導向

式程式設計的方法來開發軟體,要比傳統的方法快上 10 到 100 倍。

‧ 許多專家對學習 C++的看法是:「先學好 C,然後再學 C++」。

1.8 Java 語言

‧ Sun Microsystems (昇陽)在 1991 年成立了一個名為 Green 的國際合作研究計畫,這個計畫

根據 C 和 C++語言開發一種適合智慧型消費性電子產品的程式語言,即 Java 語言。1993年全球資訊網開始蓬勃發展,Sun 看見了這個商機,即利用 Java 來撰寫具有動態內容的網

頁,於 1995 年 5 月發表時,立即引起了商業界的高度興趣。

‧ Java 語言目前被用來製作互動型動態網頁、開發大型商用軟體、網際網路應用程式及消費

- 8 -

性電子產品應用程式等用途上。

1.9 C 程式的開發程序

‧ C 程式從撰寫到執行,通常需要經過以下 6 大步驟:

1. 編輯(edit):純文字檔(ASCII code),C 程式的副檔名為.c2. 前置處理(pre-process):根據 C 程式內的前置處理命令(如#include, #define 等)將其他

檔案含括進來,或將程式中的特殊符號用某些文字予以替換。

3. 編譯(compile):將 C 程式轉換成機器語言碼(又稱目的碼/object code),輸出副檔名為.obj4. 連結(link):將所有相關的.obj 檔連結起來以得到完整的可執行檔(.exe 檔)5. 載入(load):將可執行檔載入主記憶體

6. 執行(execute):CPU 讀取並執行可執行檔內的每一條指令

1.10 重要概念

‧ 有些程式設計師會以能夠寫出艱深且複雜的程式而自豪,其實這是一種不好的習慣,會使

程式不易閱讀、測試及除錯(debug)。程式設計師應養成撰寫結構良好且清晰易懂程式的習

慣。

‧ 請使用簡單且直接的方式來撰寫程式─ Keep it simple!千萬不要把程式寫得複雜奇特而不易閱讀。

‧ C 程式雖然相當具有可攜性(portability),但我們還是有機會遇到一些不相容的問題,我們

手上的某些 C 程式,有可能無法順利在其他編譯器(compiler)上編譯完成。

‧ 編譯器是學習程式語言最好的老師,唯有透過它,你才知道真正的對、錯。

‧ 「編譯器一定是對的」,如果有一天你懷疑它出錯了,不用懷疑,那一定是你自己的錯。

‧ Homework for chap. 1 全部(自我測驗 1.1, 1.2 及習題 1.3-1.10)

- 9 -

第第第 222 章章章 CCC 程程程式式式設設設計計計入入入門門門

本章目標

‧用 C 撰寫簡單的程式

‧使用簡單的輸入、輸出敘述式(statement)

‧熟悉基本的資料型別

‧瞭解電腦記憶體的觀念

‧能夠使用算數運算子

‧能夠使用判斷敘述式

2.1 從一個簡單的 C 程式開始

‧ 列印一行文字

/* A first program in C */#include <stdio.h>#include <stdlib.h>

/* function main() begins program execution */int main(){

printf(“Welcome to C!\n”);system(“PAUSE”); // hold the monitor screenreturn 0; // indicate that program ended successfully

} // end of function main()

執行結果─Welcome to C!

‧ 名詞解釋─

1. 註解(comment):有 /*…*/ 和 // 兩種用法

2. 標頭檔(head file):內容包含各種 C 標準函式庫函式的資訊和宣告,例如 stdio.h 檔。

3. 前置處理器指令(preprocessor instruction):命令前置處理器將所需之標頭檔引入到程式

裡,例如#include 指令。

4. 函式(function):C 程式中可含有一個或多個函式,其中一個必須是 main()函式,每個 C

- 10 -

程式都從 main()函式開始執行。

5. 程式區塊(block):由左右大括號{}所包圍的一段程式。

6. 字元(character):單一的數字、字母、符號稱之。

7. 字串(string):將數個字元連接在一起稱之。

8. 敘述式(statement):每個以分號(;)為結尾的程式片段稱之為一個敘述式。

‧ 重要用法─

1. 反斜線(\)為脫序字元(escape character)的一種,用以指示 printf()函式印出一些特殊字

元,常見用法如下:

例一: printf(“Welcome to C!\n”);等於

printf(“Welcome”);printf(“to C!\n”);

例二: printf(“Welcome\nto\nC!\n”);之執行結果為

WelcometoC!

2. 「return」:每個函式的呼叫都必須將計算結果回傳,return 0 表示回傳的結果為整數 0。

‧ 重要觀念─

1. 標準函式庫的函式(如 printf()或 scanf())並不是 C 語言的一部分,當編譯器(compiler)在

編譯函式時,它只是在目的碼(object code)中空出一塊空間來呼叫該函式,直到連結器

(linker)進行連結時,才會把該函式的目的檔一起連結成可執行檔。

2. 單一 C 程式被編譯時,編譯器並不檢查其內所呼叫函式之存在與否,直到連結器進行

- 11 -

連結時,才會進行檢查。

3. 程式撰寫時應注意「層次化」,每一層往內縮排一個單位(一般為 3 個空格),如此可強

化函式的結構性,讓程式更容易被閱讀。

2.2 另一個簡單的 C 程式

‧ 將兩個整數相加

/* An addition program */#include <stdio.h>

/* function main() begins program execution */int main(){

int integer1; // first number to be input by userint integer2; // second number to be input by userint sum; // variable in which sum will be stored

printf(“Enter first integer\n”); // promptscanf(“%d”, &integer1 ); // read an integer

printf(“Enter second integer\n”); // promptscanf(“%d”, &integer2 ); // read an integer

sum = integer1 + integer2 ; // assign total to sumprintf(“Sum is %d\n”, sum); // print sum

system(“PAUSE”); // hold the monitor screenreturn 0; // indicate that program ended successfully

} // end of function main()

執行結果─Enter first integer45Enter second integer72Sum is 117

- 12 -

‧ 重點提示─

1. 宣告(declaration):定義變數的名稱與其類型。

2. 宣告必須放在任何可執行敘述式之前。

3. 宣告與可執行敘述式間應以一空行分開,以增加程式的可讀性。

4. 每個逗號( , )、等號( = )、加號(+)、減號(-)、乘號( * )、除號( / )前後都加上一個空格

使程式的可讀性更高。

5. 識別字(identifier):識別字是一串字元(即字串),但第一個字元不可以是數字。識別字

主要是作為變數或函式的名稱用。ANSI C 的編譯器只識別前 31 個字元。在 C 裡,大

小寫是不同的字母,所以 a1 和 A1 代表不同的識別字。

6. 識別字應選取有意義的名稱以增進程式本身的可閱讀性。識別字作為一般變數用途,

其第一個字母習慣用小寫。

7. 數個單字所組成的有意義變數名稱可以增進程式的可讀性。

例如: totalcommissions (避免)

total_commissions (可用)

totalCommissions (建議)

8. 百分比(%)為脫序字元(escape character)的一種,用以指明輸入資料之型別,「%d」表

示其型別為十進制(decimal)整數。

9. 一個%脫序字元須對應一個參數。

10. 「&」稱為位址運算子(address operator),「&integer1」表示 integer1 整數的記憶體位址。

11. scanf()函式內的變數一律加上&運算子;printf()函式內一律直接使用變數名稱。

12. 「=」稱為指定運算子(assignment operator),包含指定運算子的敘述式叫做指定敘述

式,大部分的運算都發生在指定敘述式上。

13. 指定敘述式計算的部分應放在「=」運算子的右邊。

14. 當編譯器無法辨識某一行指令或敘述式時,便會以語法錯誤(syntax error)提出警告。此

時,編譯器通常會印出錯誤訊息,以協助程式設計師找出並修正該錯誤。

15. printf()函式裡可以包含運算式,例如:sum = integer1 + integer2 ;printf(“Sum is %d\n”, sum );

上兩行等於

printf(“Sum is %d\n”, integer1 + integer2 );

16. 「記憶體位址」的概念:像 integer1、integer2 和 sum 這樣的變數名稱,每一個都會對

應到電腦理的一個記憶體位址。請注意!任兩個記憶體位置間未必彼此相鄰。

17. 記憶體位置的「破壞性讀入」(destructive read-in)與「非破壞性讀出」(non-destructive

- 13 -

read-out)。

2.3 C 的算數運算

‧ 算數運算子(arithmetic operators):。

‧ 重點提示─

1. 算數運算子都是二元運算子(binary operators),即運算子進行運算時需要兩個運算元。

2. 模數除法(declaration):整數相除取餘數,例如:「7%4 等於 3」,「17%5 等於 2」。模數

運算子是整數運算子,它的運算元必須是整數。

3. 除以零這個動作通常在電腦系統裡都是未定義的,它會造成嚴重的錯誤,這個錯誤發

生時將立即中止程式的執行。

4. 計算機儲存的可以運算的數,有整數(integer)與浮點數(floating-point number)兩種,其

中整數又分短整數(short integer)與長整數(long integer),浮點數又分單精數(single pre-

cision number)與倍精數(double precision number)。

5. 算數運算式5

a b c d e 之輸入法為 ( ) / 5a b c d e

6. 在無法確定複雜運算式內的運算順序時,請善加利用「括號」來強制執行運算順序,

例如:算數運算式 % /p r q w x y 請寫成 ( )% /p r q w x y 。

- 14 -

7. 算數運算子的執行步驟:例如

2.4 C 的邏輯運算

‧ 邏輯運算子(logic operators):

‧ 重點提示─

1. 「==」、「!=」、「>=」和「<=」這四個運算子的兩個符號間沒有空格。

2. 「!=」、「>=」和「<=」不能寫成「=!」、「=>」和「=<」。

3. 「==」為邏輯運算子,其與指定運算子「=」不同,不可混用。

- 15 -

2.5 C 的關鍵字

‧ 完整的 C 的關鍵字(keywords)如下圖所示:

Keywords

auto double int struct

break else long switch

case enum register typedef

char extern return union

const float short unsigned

continue for signed void

default goto sizeof volatile

do if static while

Fig. 2.15 C’s reserved keywords.

。這些字對 C 編譯器而言已經具有特定的意義,因此程式設計師不可拿來做為識別字(如

變數名稱等)。

。C 語言的所有關鍵字皆由小寫字母所組成。

。C 語言有分大、小寫;FORTRAN 語言不分大、小寫。

‧ Homework for chap. 2 自我測驗 2.1-2.6(有解答), 習題 2.7, 2.9, 2.12-2.17, 2.22, 2.28

- 16 -

第第第 333 章章章 結結結構構構化化化程程程式式式設設設計計計

本章目標

‧瞭解解決問題的基本技術

‧能夠經由從上而下、逐步改良的過程來發展演算法

‧能夠使用選擇敘述式 if 及 if-else 來選擇所要執行的動作

‧能使用 while 重複敘述式來重複執行敘述式

‧使用計數器和警示訊號來控制重複結構

‧能夠使用遞增、遞減及設定等運算子

3.1 演算法與虛擬碼

‧ 任何在電腦上執行的問題,本質上都是由一系列有特定執行順序的動作所組成。該一系列

動作稱為解決該問題的演算法。

‧ 演算法(algorithm)

。解決問題的步驟與方法

。可執行、有次序、不含糊(或很明確)、有終點的一連串動作

。例如:某主管一大早起床後所做的事a. 起床b. 脫睡衣c. 洗澡d. 穿衣服e. 吃早餐f. 開車上班

如果把洗澡跟穿衣服的順序調換,該主管將會穿著濕的外衣去上班

。程式本身就是編譯器可以編譯然後電腦可以執行的一種演算法。

‧虛擬程式碼(pseudocode)(或稱虛擬碼)

。一種給人看的非正式語言,在程式設計師開始撰寫程式前,用來幫助他們發展演算法。

。一種「紙上談兵」的演算法

。一種電腦還無法立即編譯和執行的演算法

- 17 -

。任何用非程式語言所撰寫的演算法,都是一種虛擬碼。

。虛擬程式碼所寫的程式並不能在實際的電腦上執行,它們是用來幫助程式設計師在真正

以程式語言來撰寫程式前,「思考」這個程式如何撰寫。

。一份經過仔細設計的虛擬程式碼,可以很快地被轉換成對應的 C 程式。

3.2 控制結構

‧ 「控制結構」指控制程式如何進行的方法,所有的程式都可以由循序結構、選擇結構與重

複結構這三種結構寫成。

‧ C 語言提供有 7 種控制敘述式,分別為循序一種、選擇三種、重複三種。

‧ 循序結構(sequence structure)

。程式中的敘述式(statement)或指令(instruction)是以它們在程式中出現的順序一個接著一

個被執行,這種作法叫做循序式執行(sequence excution)或循序結構。

。循序結構是大部分程式語言的內建特性。

。1960 年代,人們發現任意移轉程式執行的位置,將會使得軟體的發展愈來愈困難。批

評的焦點都集中在 goto 指令上,因為它可讓程式設計師任意移轉程式執行的位置(移轉

程式控制權),大大地破壞了程式的結構與可讀性。於是「消除 goto」幾乎成了結構化

程式設計的同義詞。

。1970 年代,人們才逐漸嚴肅地面對結構化程式設計習慣之養成,他們發現,以這種方

式所撰寫出來的程式較清晰易懂,且更容易偵錯。

。1996 年 Bohm 和 Jocopini 的一份研究告訴我們,所有的程式都可以由循序、選擇與重複

三種控制結構寫成。

‧ 選擇結構(selection structure)

。C 語言以敘述式的形式提供了三種選擇結構:

1) 單一選擇結構:使用 if 敘述式

例一:if ( grade >= 60 ) printf( “Passed\n” );等於if ( grade >= 60 ) printf( “Passed\n” );

例二:

- 18 -

if ( grade >= 60 ) { printf( “Passed\n” ); printf( “Congratulations!\n” );}

2) 雙重選擇結構:使用 if… else 敘述式

例一:if ( grade >= 60 ) printf( “Passed\n” );else printf( “Failed\n” );

例二:if ( grade >= 90 )printf( “A\n” );

else if ( grade >= 80 ) printf( “B\n” );else if ( grade >= 70 ) printf( “C\n” );else if ( grade >= 60 ) printf( “D\n” );else printf( “F\n” );

3) 多重選擇結構:使用 switch 敘述式

例一:grade = getchar();switch ( grade ) { // start switch

case‘A’:case‘a’:

++aCount; // increment aCountbreak;

case‘B’:case‘b’:

++bCount; // increment bCountbreak;

case‘C’:case‘c’:

++cCount; // increment cCountbreak;

case‘D’:case‘d’:

++dCount; // increment dCountbreak;

case‘F’:case‘f’:

++fCount; // increment fCountbreak;

} // end switch

- 19 -

‧ 重複結構(sequence structure)

。C 語言以敘述式的形式提供了三種重複結構:

1) 有限次數重複結構:使用 for 敘述式

例一:從數字 1 分行列印到數字 10int i;for ( i=1 ; i <= 10 ; i++ ) {

printf( “i = %d\n” , i );}

2) 前條件重複結構:使用 while 敘述式

例一:int product;product = 1;while ( product <= 2000 ) {

printf(“product = %d\n”, product );product = 2 * product;

}

3) 後條件重複結構:使用 do… while 敘述式

例一:int product;product = 1;do {

printf(“product = %d\n”, product );product = 2 * product;

} while ( product <= 2000 );

。如果 while 敘述式內,沒有任何一個動作能讓 while 的條件式變成「偽(false)」,這種結

構將不會停止,此種錯誤稱為「無窮迴圈」。

‧ 流程圖(flowchart)

。流程圖是整個演算法或部分演算法的一種圖形表示法。

。流程圖的繪製,乃使用具有特定意義的圖形標誌來繪製,像是矩形、菱形、平行四邊形

和圓形等,圖形間再用稱為流向(flowline)的箭頭連接起來。

。流程圖和虛擬程式碼一樣,都對演算法的發展和表示非常有幫助。流程圖可以清楚地表

示出程式中結構控制的運作情形;虛擬程式碼則可幫助程式之順利撰寫。

- 20 -

3.3 重複結構控制實例

‧ 大多數的程式在邏輯上都可分成三大部分,一是「初始階段」的資料的輸入或設定初值,

二是「處理階段」的資料計算和處理,三是「結束階段」的結果列印和儲存。

‧ 經驗顯示,利用電腦解決問題時,最困難的部分便是演算法的開發。一旦有了正確的演算

法,接下來便很容易寫出可以執行的 C 程式。

‧ 有些程式設計師沒有先寫虛擬碼便開始撰寫程式,他們認為撰寫虛擬碼只會延遲程式開發

的進度。

問題:發展一個求全班平均成績的程式

‧ 計數器控制的重複結構

。程式碼─

#include <stdio.h>

int main(){

int counter;int grade;

- 21 -

int total;float average;

counter = 0;total = 0;while ( counter <= 10 ) {

counter = counter + 1; printf( “Enter grade: ” ); scanf( “%d” , &grade );

total = total + grade;}average = ( float ) total / 10;

printf( “Class average is %.2f\n” , average );

system( “PAUSE” );return 0;

}

。「計數器控制的重複結構」通常也稱為「確定次數的重複結構」,因為重複的次數在迴圈

開始執行前便已確定。

‧ 警示值控制的重複結構

。程式碼─

#include <stdio.h>

int main(){

int counter;int grade;int total;float average;

printf(“Enter grade, -1 to end:”);scanf(“%d”, &grade );

counter = 0;total = 0;while ( grade != -1 ) {

counter = counter + 1;total = total + grade;printf(“Enter grade, -1 to end:”);scanf(“%d”, &grade );

}

if ( counter != 0 ) {average = ( float ) total / counter;

printf( “Class average is %.2f\n” , average );}else {

- 22 -

printf(“No grades were entered\n” , average );}

system( “PAUSE” );return 0;

}

。「警示值控制的重複結構」通常也稱為「不確定次數的重複結構」,因為重複的次數在迴

圈開始執行前並不確定。

。在警示值控制迴圈裡,要求資料的提示訊息應明確地告訴使用者警示值為何。

。警示值的選擇不能是可能的輸入值。

3.4 一些有用的運算子

‧ 指定運算子(assignment operator)

。C 語言提供了數種指定運算子,使得指定運算式的寫法可以縮短。

。因為有降低可讀性之疑慮,非有特殊理由或原因,不建議使用上述指定運算子。

‧ 遞增、遞減運算子(increment and decrement operator)

- 23 -

。等效長運算式─

「a = ++passes + 2;」 相當於 「passes = passes + 1; a = passes + 2」

「a = passes++ + 2;」 相當於 「a = passes + 2; passes = passes + 1」

。因為有降低可讀性之疑慮,非有特殊理由或原因,不建議使用遞增及遞減運算子。

。若出現在單一敘述式時,前置或後置遞增(減)運算子的效果是一樣的,即

「passes = passes + 1;」 相當於 「++passes;」 相當於 「passes++;」「passes = passes - 1;」 相當於 「--passes;」 相當於 「passes--;」

。只有當出現在長運算式中,前置或後置遞增(減)運算子的效果才會不一樣。

。遞增(++)或遞減(--)運算子只能使用在單純的變數名稱上,不可以使用在一個運算式上,

例如: ++(x+1) 是個錯誤的語法

‧ Homework

自我測驗 3.1-3.10(有解答), 習題 3.11, 3.13, 3.29, 3.30, 3.33, 3.34

- 24 -

第第第 444 章章章 CCC 程程程式式式控控控制制制

本章目標

‧詳細程式控制的基本概念

‧介紹 break 和 continue 指令之使用

‧能夠使用邏輯運算子

4.1 基本概念

‧ 迴圈(loop):

。一種用於重複執行某些指令的控制結構。

。當迴圈繼續的條件式為真時,電腦會重複執行其內的指令。

‧ 兩種迴圈製作的方式或重複控制的結構─

1. 計數器控制的重複:需要有

1) 控制變數的名稱

2) 控制變數的初始值(initial value)

3) 判斷執行的條件式是否滿足

4) 每一次迴圈執行後,控制變數的遞增或遞減量

2. 警示值控制的重複:需注意

1) 用於重複次數無法事先預知的情況

2) 迴圈內需含有一個警示值讀取的敘述式

3) 讀到警示值代表「迴圈執行結束」

4) 警示值與其他輸入資料的類型相同,但不得出現重複情形

‧ 重點提示 1─

。請使用整數值來控制迴圈的計數。

。請為控制結構本體內的每個敘述式進行適當的縮排。

。請在每個控制結構的前後各加一行空行,以增加可讀性。

。太多層的縮排反而會破壞可讀性,一般而言,應嘗試不要使用超過三層的縮排。

- 25 -

‧ for 迴圈結構

‧ for 與 while 之等效迴圈結構

。迴圈結構

for ( expression1 ; expression2 ; expression3 ){

statement1;statement2;…

}

等效於

expression1;while (expression2 ) {

statement1;statement2;…expression3;

}

。例如─

for (counter = 1 ; counter <= 10 ; counter++ ){…

- 26 -

}

等效於

counter = 1;while ( counter <= 10 ) {…counter++;

}

。因為遞增動作是在 for 迴圈本體執行後才進行的,因此在這裡「++counter;」、

「counter++;」、「counter = counter + 1;」、「counter +=1;」的作用相同。

。也因為與實際情況較為相符之故,大部分程式設計師在 for 迴圈的 expression3 中偏好使

用後置遞增格式「counter++;」。

‧ 重點提示 2─

。為避免重複次數出現誤差(如多算或少算一次),應養成初始值設定為「1」及條件式內

使用關係運算子「<=」之習慣。

。「for 的敘述式裡的三個運算式(expression1, expression2 和 expression3)都是可有可無的」,

這句話語法正確,但在作法上則非常不建議。

─ 定義控制變數初始值的 expression1 可以放在之前的其他程式位置。

─ 如果省略了 expression2 的話,則 C 會認為控制條件永遠為真因而建立一個無窮迴圈。

─ 如果遞增動作放在迴圈本體內,則 expression3 便可省略。

。for 敘述式內的兩個分號不能省略

。如果迴圈繼續條件 expression2 一開始為偽,則迴圈的本體將一次都不執行,程式會接

著由 for 敘述式後的第一個敘述式繼續執行。

‧ 一個 for 迴圈結構的例子:利用 for 來計算複利

#include <stdio.h>#include <math.h>

int main(){

double amount;double principal = 1000.0;double rate = .05;int year;

printf( “%4s%21s\n”, “year”, “Amount on deposit” );

for ( year = 1 ; year <= 10 ; year++ ) {

- 27 -

amount = principal * pow( 1.0 + rate , year ); printf( “%4d%21.2f\n”, year, amount );}

system( “PAUSE” );return 0;

}

‧ switch 選擇結構

grade = getchar();switch ( grade ){

case‘a’:aCount = aCount + 1;break;

case‘b’:bCount = bCount + 1;break;

case‘c’:cCount = cCount + 1;break;

default: printf( “Incorrect letter entered.” );

break;}

。switch 控制結構與其他控制結構不同,它並不需要為某一個 case 裡的數個動作加上大括

- 28 -

號。

‧ break 與 continue 指令

。break 與 continue 指令可以用來改變程式的控制流程

。break 用於 for, while, do…while 和 switch 敘述式內,break 指令會終止目前所在的這一

層選擇或重複結構,電腦會接續執行該結構後的下一行指令。

。continue 用於 for, while, do…while 的敘述式內,continue 指令不會終止目前所在的這一

層重複結構,只是要求電腦結束該次的重複動作(即略過 continue 後的那些指令),而去

接續執行下一次的重複動作。

。某些程式設計師覺得 break 和 continue 指令會破壞程式的結構,所以他們會盡量避免使

用 break 和 continue 指令。

。在獲取「良好軟體工程」和「最佳執行效率」之間,經常存在矛盾,通常是魚與熊掌不

可兼得。

。break 的使用例

#include <stdio.h>int main(){

int x;

for( x=1 ; x <= 10 ; x++ ) {if ( x == 5 ) {

break;}printf(“%d”, x );

}

printf(“\nBroke out of loop at x == %d\n”, x );

system(“PAUSE”);return 0;

}

執行結果─1 2 3 4Broke out of loop at x == 5

。continue 的使用例

#include <stdio.h>int main(){

int x;

- 29 -

for( x=1 ; x <= 10 ; x++ ) {if ( x == 5 ) {

continue;}printf(“%d”, x );

}

printf(“\nBroke out of loop at x == %d\n”, x );

system(“PAUSE”);return 0;

}

執行結果─1 2 3 4 6 7 8 9 10Broke out of loop at x == 11

‧ 邏輯運算子(logic operator)

。邏輯運算子與關係運算子( > , < , >= , <= , == , != )不同。

。邏輯運算子與指定運算子( = , += , -= , *= , /= , %= )不同。

。C 提供的邏輯運算子,包括 && (邏輯 AND)、|| (邏輯 OR)、!(邏輯 NOT),可以將數個

簡單的條件式,組合成一個複雜的條件式。

。為節省可能的執行時間,在使用&&運算子的式子裡,請將最有可能為偽的條件放在&&

的左邊;而在使用||運算子的式子裡,請將最有可能為真的條件放在||的左邊。

‧ Homework

自我測驗 4.1-4.4(有解答),

習題 4.5, 4.6, 4.8, 4.9, 4.10, 4.38

- 30 -

第第第 555 章章章 函函函式式式

本章目標

‧瞭解如何用函式來建構模組化的程式

‧學習如何撰寫及呼叫自己的函式

‧瞭解函式間如何傳遞所需的資訊

‧學習使用亂數產生函式

5.1 基本概念

‧ 真實的程式比我們想像的大很多,為求程式發展及維護之順利與成功,必須以模組化的方

式,將大程式分解成一些較小的程式,以便將小程式「各個擊破」。C 語言所提供的模組

稱為函式(function),FORTRAN 語言所提供的模組稱為副程式(subroutine)。

‧ C 標準函式庫提供了包羅萬象的函式,包括常用的數學運算函式、字串及字元處理函式、

輸入/輸出函式、亂數產生函式等等。這些函式提供了程式設計師所需的大部分功能,可

大大地減輕程式設計師的負擔。

‧ 函式是經由呼叫(call)的方式被引用,函式呼叫的意義有

1) 指定名稱:指明欲引用之函式名稱

2) 提供引數:提供所需之引數(argument)給被呼叫函式

3) 執行工作:執行被呼叫函式所定義的工作。

例如:「老闆函式」呼叫「員工函式」,員工函式再呼叫「其他員工函式」來完成某項工作。

- 31 -

‧ 分辨引數(argument)與參數(parameter)之不同:原函式呼叫被呼叫函式時,所傳過去的值叫

做引數;傳入被呼叫函式、用來定義內部動作的變數叫做參數。

‧數學函式庫

。使用數學函式庫時,請用前置處理器命令#include <math.h>將數學標頭檔含括進來。

。數學函式庫中所有函式的傳回資料型別都是倍精度浮點數(double-precision float),簡稱

倍精數或 double。而單精度浮點數(single-precision float),簡稱單精數或 float。

。請注意!double 型別和 float 型別的值一樣,都可以用轉換指定詞「%f」來輸出。

。數學函式的引數(argument)可以是常數、變數或運算式。

5.2 函式(function)

‧ 重點提示─

。函式讓程式設計師能夠模組化一個程式。

- 32 -

。在模組化的大程式(含有許多函式的程式)裡,主程式 main()應由一群呼叫函式的敘述式

所組成。

。程式模組化的好處:

1. 各個擊破:各個模組可以各個擊破,程式管理與寫作更容易。

2. 重複使用:模組的建立可方便其重複使用。

3. 避免重複:避免程式重複出現相同的程式片段。

。所有宣告在函式定義裡的變數都是區域變數(local variable),即「這些變數只定義在該函

式內」或說「只有該函式才知道這些變數的存在」。區域變數在該函式執行結束後即自

行消失。

。函式的名稱應能充分反映出它所執行的工作。如果你無法選用一個恰當的名稱來代表函

式的功能,那可能是這個函式的功能太雜了,需要將之切割成更小的函式。

‧ 函式定義的格式如下─

return-value-type function-name( parameter-list ) // 傳回值型別 函數名稱(參數清單){

definitions; // 定義或宣告statements; // 指令

}

。其中,若 return-value-type 為 void 的話,表示此函式沒有傳回值。雖然當回傳型別被省

略時,其內定型別為 int,不過應養成將回傳型別明確寫出的習慣。

。每個 parameter-list 的型別都必須明確列出,如果沒有列出的話,C 編譯器會將之視為 int

型別。

。分辨不同: 1) void name1( double x , y ) 等效於 void name1( double x , int y )不等於 void name1( double x , double y )

2) double x , y; 等效於 double x; double y;

‧ 將執行指令的控制權由被呼叫函式返還原呼叫程式的方式有三種:

1. 自動返還:當執行到函式的終了時,控制權自動返還原呼叫程式,沒有 return 指令。

2. 不傳值返還:使用「return;」指令。

3. 有傳值返還:使用「return expression;」指令。

‧ 程式例 1─利用自訂的 square()函式來計算 1 到 10 的平方

#include <stdio.h>#include <stdlib.h>

- 33 -

int square( int ); // 函式宣告

int main() // 主程式{

int x;

for ( x = 1 ; x <= 10 ; x++ ) { printf( “%d ”, square( x ) );}

printf( “\n” ); system( “PAUSE” );

return 0;}

int square( int y ) // 函式定義{

return y*y;}

執行結果─2 4 9 16 25 36 49 64 81 100 (1B)

‧ 程式例 2─利用自訂的 maximum()函式求取三整數中的最大者

#include <stdio.h>#include <stdlib.h>

int maximum( int , int , int ); // 函式宣告

int main() // 主程式{

int number1;int number2;int number3;

printf( “Enter three integers:” );scanf( “%d%d%d”, &number1 , &number2 , &number3 );

printf( “Maximum is: %d\n”, maximum( number1 , number2 , number3 ) ); system( “PAUSE” );

return 0;}

int maximum( int x1 , int y1 , int z1 ) // 函式定義{

int max = x1; // 假定 x 為最大值if ( y1 > max ) max = y1;if ( z1 > max ) max = z1;return max;

}

- 34 -

執行結果─Enter three integers: 22 85 17Maximum is: 85

‧傳值呼叫與傳址呼叫

。傳值呼叫(call by value):

1. 傳值呼叫:傳遞引數時,引數值會在被呼叫函式內複製一份,對這些數值進行修改,

不會影響到原來的值。

2. 對 C 來說,所有的函式呼叫都是傳值呼叫。

3. 當被呼叫函式不需修改呼叫者的原始變數時,應使用傳值呼叫。

例 1-1:C 的傳值呼叫#include <stdio.h>#include <stdlib.h>

int square( int x);

int main(){

int length = 4;

printf( “square = %d\n”, square( length ) );printf(“length = %d\n”, length );

system( “PAUSE” );return 0;

}

int square( int x1 ){

x1++;return x1 * x1;

}執行結果─square = 25length = 4

例 1-2:C 的傳址呼叫#include <stdio.h>#include <stdlib.h>

int square( int *x);

int main(){

int length = 4;

printf( “square = %d\n”, square( &length ) );

- 35 -

printf(“length = %d\n”, length );

system( “PAUSE” );return 0;

}

int square( int *x1 ){

(*x1)++;return (*x1) * (*x1);

}執行結果─square = 25length = 5

。傳址呼叫(call by address):。

1. 傳址呼叫:傳遞引數時,引數值直接交給被呼叫函式使用,對這些數值進行修改,

將直接改變原來的值。

2. 對 FORTRAN 來說,所有的函式呼叫都是傳址呼叫。

3. 當被呼叫函式需要修改呼叫者的原始變數時,應使用傳址呼叫。

。傳址呼叫(call by address)又叫做傳參考呼叫(call by reference)。

‧三元運算子「? :」

。語法─「(運算元 1) ? (運算元 2) : (運算元 3)」

1. (運算元 1)會先被運算

2. 如果運算結果得到非零的數值,則進行(運算元 2)之運算;如果得到零的數值,則進行(運算元 3)的運算。

例 1:(#3.29)#include <stdio.h>#include <stdlib.h>

int main(){

int count = 1;

while ( count <= 10 ) { printf( “%s\n”, count%3 ? “****” : “++++” );

count++;}

system( “PAUSE” );return 0;

}

- 36 -

例 2-1:(#3.34) 使用 if… else… (產生一個由星號包圍起來的中空正方形)#include <stdio.h>#include <stdlib.h>

int main(){

int i, j, width;

printf(“Enter the width (1-20):”);scanf(“%d”, &width );

for ( i = 1 ; i <= width ; i++ ) {printf( “*” );if ( i==1 || i==width )

for ( j = 2 ; j <= width-1 ; j++) printf( “*” );else

for ( j = 2 ; j <= width-1 ; j++) printf( “ ” );printf( “*\n” );

}

system( “PAUSE” );return 0;

}

例 2-2:(#3.34) 使用 for…for ( j = 1 ; j <= width ; j++) printf( “*” );printf( “\n” );for ( i = 2 ; i <= width-1 ; i++ ) {printf( “*” );for ( j = 2 ; j <= width-1 ; j++) printf( “” );printf( “*\n” );

}for ( j = 1 ; j <= width ; j++) printf( “*” );printf( “\n” );

例 2-3:(#3.34) 使用三元運算子「?:」for ( j = 1 ; j <= width ; j++) printf( “*” );printf( “\n” );for ( i = 2 ; i <= width-1 ; i++ ) {

for ( j=1 ; j<=width ; j++ ) printf( “%s”, (j-1)%(width-1) ?“”:“*”);printf( “\n” );

}for ( j = 1 ; j <=width ; j++) printf( “*” );printf( “\n” );

例 2-4:(#3.34) 使用三元運算子「?:」之更短寫法for ( i = 1 ; i <= width ; i++ ) {

for ( j=1 ; j<=width ; j++ ) printf( “%s” , ((i-1)%(width-1)) * ((j-1)%(width-1)) ? “ ” : “*” );printf( “\n” );

}

- 37 -

‧亂數產生函式

。函式「rand()」:

1. 產生一個介於 0 到 RAND_MAX(定義在<stdlib.h>的正整數)之間的整數。

2. ANSI 標準規定 RAND_MAX 的值至少需為 32767。

3. 每一次呼叫 rand()時,0 到 RAND_MAX 之間的每一個整數應有相同的機會被選到。

4. rand()所產生的值的範圍,通常不合乎某個特定應用的需求,此時可配合模數運算

子「%」,例如模擬擲骰子的 6 個面

rand() % 6 + 1

來產生 1 到 6 的整數。這種作法叫做比例化(scaling)及平移(shift),6 稱為比例因子

(scaling factor)。

例 1:#include <stdio.h>#include <stdlib.h>

int main(){

int i, times_max = 10;

for ( i = 1 ; i <= times_max ; i++ ){printf( “%5d”, 1 + ( rand() % 6 ) );if ( ( i % 5 ) == 0 ) printf( “\n” );

}

system( “PAUSE” );return 0;

}

執行結果─6 6 5 5 65 1 1 5 3

5. 當我們再執行程式一次時,令人驚訝的是會得到一模一樣的結果,這種重複性是

rand()的特性。呼叫 rand()所產生的數列看起來是隨機的,可是每次執行都會出現相

同的數列。

6. 問題:上述 1 到 6 出現的機率是否完全相等?決定的因素在哪裡呢?

。「srand()」函式之使用

1. srand()需要一個 unsigned 資料型別的整數當引數,它為 rand()提供種子(seed),讓

- 38 -

rand()執行時產生不同順序的亂數。

2. unsigned 資料型別是 unsigned int 的簡稱。一個 2Bytes 長度的 int 變數,可以存放

-32768 到+32767 之間的正負整數;而一個 2Bytes 長度的 unsigned 變數,可以存放

0 到+65535 之間的正整數。

3. 可以用 scanf()裡 %u 來從鍵盤讀入一個 unsigned 變數的值。srand()函式定義在

<stdlib.h>裡。

例 2:#include <stdio.h>#include <stdlib.h>

int main(){

int i, times_max = 10;unsigned seed;

printf(“Enter seed:”);scanf(“%u”, &seed );

srand( seed );

for ( i = 1 ; i <= times_max ; i++ ){printf( “%5d”, 1 + ( rand() % 6 ) );if ( ( i % 5 ) == 0 ) printf( “\n” );

}

system( “PAUSE” );return 0;

}執行結果─Enter seed: 676 1 4 6 21 6 1 6 4

Enter seed: 8672 4 6 1 61 1 3 6 2

Enter seed: 676 1 4 6 21 6 1 6 4

4. 當輸入不同的 seed 值時,此程式會產生不同的亂數數列。

5. 呼叫 time( 秒數 )可以改變目前電腦內部時鐘的日期及時間;呼叫 time( NULL )則

可以讀取電腦內部的時鐘,並傳回一個以秒來計數的目前的日期及時間

- 39 -

5.3 儲存類別

‧ 識別字(identifier)為一字串(string),其起始字元不得為數字或某些已有定義之特殊符號、

中間不能出現空格。

‧ 識別字主要作為函式或變數名稱。

‧ 識別字除了作為名稱外,還需要定義其儲存類別(storage class),C 提供了四種變數的儲存

類別,即四種不同的記憶體佔用方式:

1. auto(自動儲存類別):

。 區域變數內定為自動儲存類別,一般均省略不寫。

。 自動儲存是節省記憶體的一種方法,因為當程式離開此函式時,便將之清除。

2. register(登記儲存類別):

。 此宣告之作用為「建議編譯器將此變數放到快速的 CPU 暫存器中,以節省 CPU 與

記憶體間的存取時間。」

。 當暫存器的數目不敷使用時,編譯器可以忽略 register 的宣告。

。 目前功能強大的編譯器可以自行分辨哪些是經常使用的變數,會自動將其放在 CPU

暫存器中,所以通常不需要做此宣告。。

3. extern(外部儲存類別):

。 全程佔用記憶體空間。

。 將變數的宣告放在任何函式定義以外(即全域變數),該變數即具有外部儲存特性。

4. static(靜態儲存類別):。

。 全程佔用記憶體空間。

。 在程式離開函式後,該函式內的 static 區域變數還會保有它們的值,當下一次再進

行呼叫時,static 區域變數的值會和此函式上次離開時的值一樣。

例:靜態變數之使用#include <stdio.h>#include <stdlib.h>

void test( void );

int main(){

test();test();test();

- 40 -

system( “PAUSE” );return 0;

}

void test(){

int count = 0;printf(“count = %d\n”, count );count +=3;

}執行結果─count = 0count = 0count = 0

若將「int count = 0;」改寫成「static int count = 0;」,則執行結果為count = 0count = 3count = 6

‧ 識別字依其有定義、有效或可以參照的範圍之不同而有檔案範圍、函式範圍與區塊範圍之

分別:

1. 檔案範圍(file scope)(大範圍):宣告在函式外的識別字都具有檔案範圍,如函式名

稱與全域變數(global variable)。

2. 函式範圍(function scope) (中範圍):宣告在函式內的識別字都具有函式範圍,如區

域變數(local variable)。

3. 區塊範圍(block scope) (小範圍):宣告在區塊內的識別字都具有區塊範圍,如暫時

變數。

例:檔案範圍、函式範圍及區塊範圍之分別#include <stdio.h>#include <stdlib.h>

int x = 1; //全域變數void func( void );

int main(){

int x = 10; //區域變數printf(“function scope: x = %d\n”, x);

{int x = 100; //暫時變數printf(“block scope: x = %d\n”, x);

}

func();

- 41 -

system( “PAUSE” );return 0;

}

void func(){

printf(“file scope: x = %d\n”, x);}執行結果─function scope: x = 10block scope: x = 100file scope: x = 1

5.4 遞迴(Recursion)

‧ 遞迴是進階電腦科學課程中所討論的一項複雜的課題。遞迴使用不當,極易產生無窮迴圈

之結果,故使用時要特別小心謹慎。

‧ 在此之前,我們所討論過的程式,其結構通常是某個函式以階層式的方式呼叫其他函式。

不過對於某些問題,如果函式可以呼叫它自己,將會十分有用。

‧ 遞迴函式(recursive function):一種可以直接或間接呼叫自己的函式。

例:遞迴呼叫#include <stdio.h>#include <stdlib.h>

long factorial( long );

int main(){

int i;

for( i = 0 ; i <= 10 ; i++ ) {printf(“%2d! = %ld\n”, i , factorial( i ) );

}

system( “PAUSE” );return 0;

}

long factorial( long number ){

if ( number <= 1 )return 1;

- 42 -

elsereturn ( number * factorial( number - 1 ) );

}執行結果─0! = 11! = 12! = 23! = 64! = 245! = 1206! = 7207! = 50408! = 403209! = 362880

10! = 3628800

‧ 遞迴與疊代(Iteration)

。 遞迴與疊代都是以控制結構為基礎:遞迴使用選擇結構,疊代使用重複結構。

。 遞迴與疊代都含有重複性,遞迴是經由重複的函式呼叫來達到重複的效果。可以使用

疊代法將前述 factorial 函式替換如下:

long factorial( long number ){

int i;long sum = 1;for ( i = 2 ; i <=number ; i++ ) sum *= i;return sum;

}

。 遞迴有許多缺點:

1. 結構較複雜:遞迴結構在邏輯上較複雜,使用上能免則免。

2. 執行較費時:呼叫函式本身即需要消耗許多 CPU 時間。

3. 記憶體消耗大:每個遞迴呼叫都需要再複製一份變數,可能會消耗可觀的記憶體。

所以,在要求效率情況下,請避免使用遞迴。

。 任何可以用遞迴法解決的問題,也一定可以用疊代法來解決。一般以疊代法為優先選

擇,可是當遞迴法 1)較能夠自然反映問題的本質,而且 2)所寫程式也不難理解時,方

建議使用遞迴法。

‧ Homework

自我測驗 #5.1-5.7(有解答),

習題 #5.11, 5.12, 5.32, 5.33, 5.39, 5.40

- 43 -

第第第 666 章章章 陣陣陣列列列(((AAArrrrrraaayyysss)))

本章目標

‧介紹陣列之資料結構

‧瞭解陣列的宣告、初始化及存取

‧瞭解如何使用陣列來進行運算與處理

‧能夠將陣列傳遞給函式

‧練習排序(sorting)

‧瞭解多維陣列之使用

6.1 基本概念

‧ 陣列(Arrays)─

。 由相同資料型別的資料項所組成的資料結構。

。 一群具有相同名稱及相同型別的記憶體位置。若要引用陣列的某一個位置或元素,只

需指出對應之陣列名稱及位置編號即可。

‧ 陣列會佔用記憶體空間,當我們宣告「int c[12];」時,便是在告訴電腦產生一個具有 12

個元素的整數陣列 c 的儲存空間,其中 c 為陣列名稱。中括號裡面的數字稱為位置編號或

下標(subscript),C 語言的起始預設值為 0 (而 FORTRAN 語言的起始預設值卻為 1),故本

例中,位置編號的變化範圍為 0 到 11。

例 1-1:

#include <stdio.h>#include <stdlib.h>#define SIZE 12 //定義一個全域常數

int main(){

int c[SIZE] = { -45, 6, 0, 72, 1543, -89, 0, 62, -3, 1, 6453, 78 }; // 給定陣列初始值int i , a = 5 , b = 6;

c[ a+ b ] += 2;for( i = 0 ; i <= SIZE-1 ; i++ ) printf( “%d ”, c[i] );printf(“\n”);

- 44 -

system( “PAUSE” );return 0;

}執行結果─-45 6 0 72 1543 -89 0 62 -3 1 6453 80

‧ 陣列並不會自動將初始值設定為零,所以程式設計師需自己進行歸零設定。

‧ 如果給定的初始值個數小於陣列的元素個數,則剩下的元素將自動指定初始值為零。

例 1-2:若將例 1-1 中 c[SIZE]之宣告及初始值設定改寫成

int c[SIZE] = { -45 };

則執行結果變為─-45 0 0 0 0 0 0 0 0 0 0 2

‧ 符號常數(Symbolic Constant):

。 符號常數是一個識別字,可以利用指令如「#define SIZE 10」來定義符號常數。

。 符號常數不佔用記憶體位置,當程式進行前置處理時,compiler 只是將所有出現符號

常數 SIZE 的地方通通代換成 10。

。 符號常數常常用來指定陣列的大小,可以讓程式更容易調整,減少需要修改的地方。

- 45 -

。 請用全大寫字母來命名符號常數,如此可以較醒目,並可提醒程式設計師該符號常數

不是一般變數。

‧ 陣列屬「靜態」資料結構,在程式執行期間其大小不能改變,它們跟一般變數一樣,會在

每次執行定義它們的區塊時建立或消除。另有「動態」資料結構,例如串列、佇列、堆疊

和樹(tree),它們可以在程式執行期間改變大小。

‧ 字元陣列

。 陣列可以存放任何型別的資料,對 C 而言,字串就是一個字元陣列,一個存放各種字

元的陣列。

。 可以使用字串常數來設定字元陣列的初始值,例如

char string1[] =“first”;

等於

char string1[] = {‘f’,‘i’,‘r’,‘s’,‘t’,‘\0’};

陣列 string1 實際上含有六個元素,即包含五個字元加上一個終止字元‘\0’。C 的所有字

串都會以終止字元做為結束。

。 因為字串實際上是字元所組成的陣列,所以我們可以使用陣列下標來直接存取字串中

的個別字元。例如,string1[0]代表字元‘f’, string1[1]代表字元‘i’,而, string1[5]代表字

元‘\0’。

‧ 字元陣列之輸出入

。 讓字元陣列能夠放得下由鍵盤所輸入的字串是程式設計師的責任,compiler 不會檢查

陣列的大小,所以 scanf 可能會將資料寫到陣列的範圍以外。

。 注意!在 scanf()中,字元陣列 string2 前不需加註位址運算子「&」符號,這是因為陣

列名稱本身即代表該陣列的起始位址。

。 用 printf()中來輸出字元陣列時,printf 不會檢查字元陣列到底有多大,它會一直列印此

陣列的字元,直到遇到終止字元‘\0’為止。

實例:

#include <stdio.h>#include <stdlib.h>int main(){

char string2[ 10 ]; // 宣告名稱及空間大小

- 46 -

scanf(“%s”, string2 ); // 鍵盤輸入printf(“%s”, string2 ); // 螢幕輸出printf(“\n”);

system(“PAUSE”);return 0;

}

其中之敘述式printf(“%s”, string2 );

相等於for ( i = 0 ; string2[ i ] !=‘\0’; i++ ) printf(“%c”, string2[ i ] );

‧ 傳遞陣列給函式

。 C 語言中,陣列的傳遞方式係以傳址方式來進行,以增進執行效率或減少 CPU 時間;

若以傳值方式來進行,因為要複製一份的關係,將耗費可觀的 CPU 時間及記憶體空間。

。 欲將整個陣列當作引數傳遞給函式時,只需給定陣列名稱即可。

。 陣列名稱實際上代表此陣列第一個元素的位址,將此位址傳遞給函式後,函式在其本

體所做之更改,便會更改到原始的陣列內容。

實例:

#include <stdio.h>#include <stdlib.h>int main(){

char array[ 5 ];

printf(“ array = %p\n”, array ); // 陣列名稱本身代表第一個元素的位址printf(“&array[0] = %p\n”, &array[0] ); // 取得第一個元素的位址printf(“ &array = %p\n”, &array ); // 取得位址的位址,&於此處無作用printf(“\n”);

system(“PAUSE”);return 0;

}

。 為避免函式本體更改原始的陣列內容,可以使用 const 指令。在函式本體內任何試圖更

改陣列內容的動作,都會造成 compile 時期的錯誤。

實例:#include <stdio.h>#include <stdlib.h>

void tryToModifyArray( const int b[] );

- 47 -

int main(){

int a[] = { 10 , 20 , 30 };

tryToModifyArray( a );

printf(“%d %d %d\n”, a[0] , a[1] , a[2] );

system(“PAUSE”);return 0;

}

void tryToModifyArray( const int b[] ){

b[ 0 ] = 2; // 錯誤, const 型態陣列不可在此更改其內容b[ 1 ] = 2; // 錯誤b[ 2 ] = 2; // 錯誤

}

6.2 多重陣列

‧ 在 C 中,二維陣列的宣告方法為

int a[3][4]; // [列 row][行 column]

‧ 陣列的資料存放方式,在 C 語言中為列導向(row major),即一列存完再存到下一列;而在

FORTRAN 語言中為行導向(column major),即一行存完再存到下一行。

實例:

#include <stdio.h>#include <stdlib.h>

int main()

- 48 -

{int i , j , k;int a[3][4] = { 10 , 20 , 30 };

for ( i = 0 ; i <= 2 ; i++ )for ( j = 0 ; j <= 3 ; j++ )

a[ i ][ j ] = i;

for ( j = 0 ; j <= 3 ; j++ ) printf(“%d ”, a[ 0 ][ j ] );for ( j = 0 ; j <= 3 ; j++ ) printf(“%d ”, a[ 1 ][ j ] );for ( j = 0 ; j <= 3 ; j++ ) printf(“%d ”, a[ 2 ][ j ] );printf(“\n”);

for ( k = 0 ; k <= 11 ; k++ ) printf(“%d ”, *( &a[ 0 ][ 0 ] + k ) );printf(“\n”);

system(“PAUSE”);return 0;

}

6.3 使用陣列來排序

實例:

#include <stdio.h>#include <stdlib.h>#define SIZE 10

int main(){

int i , pass , temp;int a[ SIZE ] = { 2 , 6 , 4 , 8 , 10 , 12 , 89 , 68 , 45 , 37 };

printf(“Data items in original order\n”);

for ( i = 0 ; i <= SIZE - 1 ; i++ ) printf(“%4d”, a[ i ] );printf(“\n”);

/* bubble sort (氣泡排序法) */for ( pass = 1 ; pass <= SIZE - 1 ; pass++ ) {

for ( i = 0 ; i < SIZE - 1 ; i++ ) {if ( a[ i ] > a[ i + 1 ] ) {

temp = a[ i ];a[ i ] = a[ i + 1 ];a[ i + 1 ] = temp;

}}

}

printf( “Data items in ascending order\n” );

- 49 -

for ( i = 0 ; i <= SIZE - 1 ; i++ ) printf(“%4d”, a[ i ] );printf(“\n”);

system(“PAUSE”);return 0;

}

‧ Homework

自我測驗 #6.1-6.5(有解答),

習題 #6.8, 6.9, 6.11, 6.17, 6.18, 6.19

- 50 -

第第第 777 章章章 指指指標標標(((PPPoooiiinnnttteeerrr)))

7.1 基本概念

‧ 指標(pointer)─

。 指標是一種變數,是一種內容指向「變數位址」的變數。

。 亦即,指標是代表「變數位址」的一種變數。

。 指標的運算與一般整數同,但通常只拿來做加多少或減多少的運算。

。 指標可以指向單一變數的位址,也可以指向陣列的任何一個元素的位址。

。 若「某指標」指向「某陣列的某個元素的位址」,則將該「指標值加 1」的意思,就是

將該指標指向下一個元素的位址。

。 指標是程式語言的強大功能之一,但也是最難駕馭的功能。C 語言擁有完整的指標功

能。

。 雖然 C 的函式呼叫一律以傳值呼叫(call by value)方式傳遞引數,但使用指標可以讓 C

程式模擬傳址呼叫(call by address)。

‧ 記憶體位址

。 可以將記憶體位址分成「實體」記憶體位址與「虛擬」記憶體位址兩種。

。 實體記憶體位址:

1. 實體記憶體位址是一種絕對位址,是作業系統與硬體間據以使用記憶體的一種位址

編號。

2. 實體記憶體通常以「一位元組(1 Bytes)」為一個單元,每個實體記憶體單元有各自

的位址編號。

3. 實體記憶體位址加 1,代表指向下一個記憶體單元。

4. 同一陣列中,各元素存放的實體記憶體位址不一定連續。

。 虛擬記憶體位址:

1. 虛擬記憶體位址是一種相對位址,是程式與作業系統間協調使用記憶體的一種位址

編號。

2. 虛擬記憶體以「能存放一個變數的位元長度」為一個單元,每個虛擬記憶體單元有

各自的位址編號,該位址編號由 compiler 產生。

- 51 -

3. 對陣列而言,虛擬記憶體位址加 1,代表指向下一個元素。

4. 同一陣列中,各元素存放的虛擬記憶體位址一定連續。

。 指標所代表的變數位址屬於後者,即虛擬記憶體位址。

‧ 指標與變數之差別

。 通常,一個變數直接存放某個數值,而一個指標存放的卻是某變數的位址。

。 對於一個被儲存於記憶體的資料或數值,變數直接參照其值,指標則間接參照其值。

。 宣告方式─ int count; //變數的宣告int *countPtr; //指標的宣告

‧ 位址運算子(address operator)「&」與指標運算子(pointer operator)「*」

。 運算子(operator):定義運算的內容或動作。

運算元(operand):被運算子拿來運算的對象或”材料”。

。 位址運算子「&」是一個會傳回運算元(一個變數)之位址的一元運算子。

指標運算子「*」是一個會傳回運算元(一個指標)所指向的物件之數值的一元運算子。

例如:

int y = 5;int *yPtr;

yPtr = &y; // 取得變數 y 的位址,並將之儲存到指標變數 yPtr 裡。// 這時,我們說:「指標 yPtr 指向變數 y」。

printf( “%d”, *yPtr ); //結果等於printf( “%d”, y );

- 52 -

實例 1:「&」與「*」效果相消

#include <stdio.h>int main(){

int y;int *Ptr;

y = 5;Ptr = &y;

/* 列印出整數值 */printf( “%d %d %d\n”, y , *Ptr , *&y ); //而「&*y」為錯誤語法

/* 列印出指標值 */ // %p 指將記憶體位址以十六進制整數印出printf( “%p %p %p %p\n”, &y , Ptr , *&Ptr , &*Ptr );

system( “PAUSE” ); return 0;}執行結果─5 5 50022FF74 0022FF74 0022FF74 0022FF74

- 53 -

實例 2:「指標加 1」的使用

#include <stdio.h>int main(){

int y1 = 1 , y2 = 2 , y3 = 3 , a[3] = { 4 , 5 , 6 };int *Ptr;

Ptr = &y1;printf( “%d %d %d %d\n”, y1 , *Ptr , *( Ptr +1 ) , *( Ptr + 2 ));

Ptr = &a[0];printf( “%d %d %d %d\n”, a[0] , *Ptr , *( Ptr + 1 ) , *( Ptr + 2 ));

system( “PAUSE” ); return 0;}執行結果─1 1 22 24 4 5 6

實例 3:二維陣列

#include <stdio.h>int main(){

int a[2][5] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };int *Ptr , i;

Ptr = a;for ( i = 0 ; i <= 4 ; i += 2 ) printf( “%d ” , *( Ptr + i ) );printf( “\n” );

Ptr = &a[0][1];for ( i = 0 ; i <= 4 ; i += 2 ) printf( “%d ” , *( Ptr + i ) );printf( “\n” );

Ptr = &a[1][0];for ( i = 0 ; i <=4 ; i += 2 ) printf( “%d ” , *( Ptr + i ) );printf( “\n” );

system( “PAUSE” ); return 0;}執行結果─1 3 52 4 66 8 10

實例 4:C 的傳址呼叫

#include <stdio.h>#include <stdlib.h>

int square( int *x);

- 54 -

int main(){

int length = 4;

printf( “square = %d\n”, square( &length ) );printf( “length = %d\n”, length );

system( “PAUSE” );return 0;

}

int square( int *x1 ){

(*x1)++;return (*x1) * (*x1);

}執行結果─square = 25length = 5

7.2 「const」(常數)修飾詞之使用─常數變數與常數指標

‧ 如果傳給某函式的值(變數值或指標值),在那個函式的本體內不希望被更改的話,則這個

值應該宣告成 const 以避免不小心更改到它。

‧ 當一個變數或一個指標被宣告為「const」常數時,如果嘗試更改其值,則編譯器在編譯階

段就會發出警告或錯誤訊息,而每一種編譯器的處理(發出訊息)方式都不盡相同。

‧ 陣列名稱本身就是一個指向陣列起始位置的常數指標。

實例 1:將字串中的小寫字元轉成大寫字元

#include <stdio.h>#include <ctype.h>

void convertToUppercase( char *sPtr );

int main(){

char string[] = “characters and $32.98”;//字元陣列(字串)初始化

printf( “字串轉換前: %s\n”, string );convertToUppercase( string ); //陣列名稱指向第一個元素之位址printf( “字串轉換後: %s\n”, string );

system( “PAUSE” ); return 0;}

void convertToUppercase( char *sPtr ){

- 55 -

while (*sPtr != ‘\0’ ) {if ( islower( *sPtr ) ) //檢查字元「*sPtr」是否為小寫

*sPtr = toupper( *sPtr ); //將該字元轉成大寫sPtr++; //指標指向下一個元素

}}執行結果─字串轉換前: characters and $32.98字串轉換後: CHARACTERS AND $32.98

實例 2:將「指標」設定為常數 (使用常數指標)

#include <stdio.h>#include <ctype.h>

void convertToUppercase( char * const sPtr ); //宣告指標為常數,其值不得修改

int main(){char string[] = “characters and $32.98”;

printf( “字串轉換前: %s\n”, string );convertToUppercase( string );printf( “字串轉換後: %s\n”, string );

system( “PAUSE” ); return 0;}

void convertToUppercase( char * const sPtr ){

int i = 0;while ( *( sPtr + i ) != ‘\0’ ) { //因為指標為常數,改以加 i 來指向下一個元素

if ( islower( *( sPtr + i ) ) ) //檢查字元是否為小寫*( sPtr + i ) = toupper( *( sPtr + i ) ); //將該字元轉成大寫

i++;}

}執行結果─字串轉換前: characters and $32.98字串轉換後: CHARACTERS AND $32.98

實例 3:將「變數」設定為常數 (使用常數變數)

#include <stdio.h>#include <ctype.h>

void convertToUppercase( const char *sPtr ); //宣告指標指向的變數值為常數

int main(){

- 56 -

char string[] = “characters and $32.98”; //字元陣列(字串)初始化

printf( “字串轉換前: %s\n”, string );convertToUppercase( string );printf( “字串轉換後: %s\n”, string );

system( “PAUSE” ); return 0;}

void convertToUppercase( const char *sPtr ){

int i = 0; while ( *( sPtr + i ) != ‘\0’ ) {

if ( islower( *( sPtr + i ) ) ) //檢查字元是否為小寫 printf( “%c”, toupper( *( sPtr + i ) ) ); //從螢幕列印出該字元

i++;}printf( “\n” );

}執行結果─字串轉換前: characters and $32.98CHARACTERSAND字串轉換後: characters and $32.98

7.3 「sizeof」運算子之使用

‧ C 提供了一個特殊的一元運算子,它用來在編譯時期計算出陣列(或其他資料型別)的大小

(以位元組 Byte 為單位)。

實例 1:計算陣列所佔之位元組大小

#include <stdio.h>

int main(){

float array[ 20 ];float *Ptr;

Ptr = array;printf( “陣列中之位元組數目為 %d\n”, sizeof( array ) );printf( “指標所指向元素的位元組數目為%d\n”, sizeof( Ptr ) );

system( “PAUSE” ); return 0;}

執行結果─陣列所佔之位元組大小為 80指標所指向元素的位元組數目為 4

- 57 -

實例 2:計算標準資料型別之位元組長度

#include <stdio.h>

int main(){ char string1[] = “first”;printf( “字元 %d\n”, sizeof( char ) );printf( “字元陣列 %d\n”, sizeof( string1 ) );printf( “短整數 %d\n”, sizeof( short ) );printf( “整數 %d\n”, sizeof( int ) );printf( “長整數 %d\n”, sizeof( long ) );printf( “浮點數 %d\n”, sizeof( float ) );printf( “倍精度浮點數 %d\n”, sizeof( double ) );printf( “長倍精度浮點數 %d\n”, sizeof( long double ) );

system( “PAUSE” ); return 0;}執行結果─字元 1字元陣列 6短整數 2整數 4長整數 4浮點數 4倍精度浮點數 8長倍精度浮點數 12

實例 3:字元、字串之輸出

#include <stdio.h>

int main(){ char string1[] = “first”;char char1 = ‘\0’;

printf( "%c\n", string1[0] );//printf( "%s\n", string1[0] ); //執行產生錯誤printf( "%c\n", *string1 );//printf( "%s\n", *string1 ); //執行產生錯誤

printf( "%c\n", char1 );printf( "%s\n", char1 );

printf( "%c\n", string1 ); //錯誤用法printf( "%s\n", string1 ); //正確用法

printf( "%c\n", &string1[0] ); //錯誤用法

- 58 -

printf( "%s\n", &string1[0] ); //正確用法

system( “PAUSE” ); return 0;}執行結果─ff

(null)‘first‘first

7.4 指標陣列(array of pointer)

‧ 陣列所存放的物件也可以是指標,稱之為指標陣列。

‧ 指標陣列常用來建構字串陣列(array of strings; string array),在此情況下,陣列中的每一個

元素都是字串。

‧ C 的字串本質上就是一個指向字串第一個字元的指標。

實例 1:一維指標陣列下標之使用

#include <stdio.h>int main(){char string1[] = “first”;char *cPtr = string1;int i;

for( i = 0 ; i <= sizeof( string1 ) - 2 ; i++ ) printf( “%c ”, cPtr[ i ] ); //指標下標法printf( “\n” );for( i = 0 ; i <= sizeof( string1 ) - 2 ; i++ ) printf( “%c ”, *( cPtr + i ) ); //指標位移法system( “PAUSE” ); return 0;

}

執行結果─f i r s tf i r s t

- 59 -

實例 2:二維指標陣列下標之使用

#include <stdio.h>int main(){const char *suit[ 4] = { “Hearts” , “Diamonds” , “Clubs” , “Spades” };int cnum[4] = { 6 , 8 , 5 , 6 };int i , k;

for( k = 0 ; k <= 3 ; k++ ) {for( i = 0 ; i <= cnum[ k ] - 1 ; i++ ) printf( “%c ”, suit[ k ][ i ] ); //指標下標法printf( “\n” );

}system( “PAUSE” ); return 0;

}執行結果─H e a r tD i a m o n d sC l u b sS p a d e s

7.5 指向函式的指標

‧ 「函式名稱」是執行此函式之「程式碼的起始位址」,指向函式的指標便是指向此起始位

置。

實例 1:指向函式之指標

#include <stdio.h>#define SIZE 10

void bubble( int data[] , const int size1 , int (*compare)( int a , int b ) );int ascending( int a , int b );int descending( int a , int b );

int main(){

- 60 -

int order , i;int a[ SIZE ] = { 2 , 6 , 4 , 8 , 10 , 12 , 89 , 68 , 45 , 57 };

scanf( “%d” , &order ); // 1 for asending order; 2 for descending order

if ( order == 1 )bubble( a , SIZE , ascending );

elsebubble( a , SIZE , descending );

/* output sorted array */for( i = 0 ; i <= SIZE - 1 ; i++ ) printf( “%5d” , a[ i ] );

system( “PAUSE” ); return 0;}

void bubble( int data[] , const int size1 , int (*compare)( int a , int b ) ){

int i , k;

void swap( int *iPtr1 , int *iPtr2 ); //宣告函式原型

/* loop for bubble sort */for ( k = 1 ; k < size1 ; k++ ) {

for ( i = 0 ; i < size1–1 ; i++ ) {if ( (*compare) ( data[ i ] , data[ i + 1 ] ) )

swap( &data[ i ] , &data[ i + 1 ] ); //資料內容對調}

}}

int ascending( int a , int b ){

return b < a; //如果 b 小於 a,傳回 1;反之,傳回 0}

int descending( int a , int b ){

return b > a; //如果 b 大於 a,傳回 1;反之,傳回 0}

void swap( int *iPtr1 , int *iPtr2 ){

int temp; //宣告一個可供暫時存放的變數

temp = *iPtr1;*iPtr1 = *iPtr2;*iPtr2 = temp;

}

- 61 -

‧ Homework

自我測驗 #7.1-7.6(有解答),

習題 #7.9, 7.10, 7.21, 7.22, 7.23, 7.30

挑戰題 #7.17

- 62 -

第第第 888 章章章 字字字元元元與與與字字字串串串

8.1 基本概念

‧ 字串(string)與字元(character)

。字元常數是一個以單引號括起來的字元,例如:‘A’,‘B’,…等。

。字元常數代表一個整數值(因為 C 的一個字元就是一個位元組的整數),例如:

printf(“%d %c %d %c \n”,‘A’,‘A’,‘B’,‘B’);其執行結果為 65 A 66 B

。字串常數則會寫在雙引號裡,例如:“John”,“Marry”,…等。

。字串是由字元組合而成,例如:

宣告 char color[] =“blue”;等於 char color[] = {“blue”};等於 char color[] = {‘b’,‘l’,‘u’,‘e’,‘\0’};

。程式語言裡,「一個字串」就是「一個字元陣列」。

。字串的名稱就是字元陣列的名稱,不管是字串的名稱或字元的陣列名稱都是一種指標,

一種指向字元陣列第一個元素位址之指標。

。字串與字元陣列的差別在於,字串的最後一定有個終止字元’\0’。

‧ 常見錯誤

。字元陣列的大小不夠存放進字串的終止字元’\0’。

。列印一個沒有終止字元’\0’的字串。

8.2 字元處理函式

‧ C 的標準字元處理函式庫包含了許多可執行字元資料測試和操作的函式。

‧ 使用字元處理函式時,需將標頭檔 ctype.h 包含進來─「#include <ctype.h>」。

- 63 -

實例:函式 iscntrl 之使用#include <stdio.h>#include <ctype.h>

int main(){

printf( "%d\n", iscntrl(‘\n’) , iscntrl(‘\t’) );printf( "%d\n", iscntrl(‘\N’) , iscntrl(‘\T’) );printf( "%d\n", iscntrl(‘%’) , iscntrl(‘$’) );printf( "%d\n", iscntrl(‘*’) , iscntrl(‘&’) );system( "PAUSE" ); return 0;

}其執行結果為32 320 00 00 0

- 64 -

8.3 字串處理函式

‧ 字串處理函式包括 1)字串轉換函式、2)字串操作函式、3)字串比較函式、4)字串搜尋函式、

5)字串處理記憶體函式及 6)其他字串相關函式。

‧ 字串轉換函式

。使用字串轉換函式時,需將標頭檔 stdlib.h 包含進來─「#include <stdlib.h>」。

實例 1:函式 atof 之使用#include <stdio.h>#include <stdlib.h>

int main(){

char s1[] = "99.0";char s2[] = "99.0%";char s3[] = "R99.0%";

printf( "%10.4f \n", atof( s1 ) );printf( "%10.4f \n", atof( s2 ) );printf( "%10.4f \n", atof( s3 ) );

system( "PAUSE" ); return 0;}其執行結果為

99.000099.00000.0000

實例 2:函式 strtod 之使用#include <stdio.h>#include <stdlib.h>

int main(){

- 65 -

char s1[] = "99.0%";char s2[] = "R99.0%";char *sPtr;double d;

d = strtod( s1 , &sPtr );printf( "%10.4f %c \n", d , *sPtr );

d = strtod( s2 , &sPtr );printf( "%10.4f %c \n", d , *sPtr );

system( "PAUSE" ); return 0;}其執行結果為

99.0000 %0.0000 R

‧字串操作函式

。使用字串操作函式時,需將標頭檔 string.h 包含進來─「#include <string.h>」。

實例:函式 strcpy 與 strncpy 之使用#include <stdio.h>#include <string.h>

int main(){

char x[] = "Happy Birthday to you";char y[ 25 ];char z[ 15 ];

printf( "%s\n", x );printf( "%s\n", strcpy( y , x ) );

- 66 -

strncpy( z , x , 14 );z[ 14] = ‘\0’;printf( "%s\n", z );

system( "PAUSE" ); return 0;}其執行結果為Happy Birthday to youHappy Birthday to youHappy Birthday

‧字串比較函式

。使用字串比較函式時,需將標頭檔 string.h 包含進來─「#include <string.h>」。

實例:函式 strcmp 與 strncmp 之使用#include <stdio.h>#include <string.h>

int main(){

char *s1 = "Happy New Year";char *s2 = "Happy Holidays";

printf( "%d\n", strcmp( s1 , s2 ) );printf( "%d\n", strcmp( s2 , s1 ) );

printf( "%d\n", strncmp( s1 , s2 , 6 ) );printf( "%d\n", strncmp( s1 , s2 , 7 ) );printf( "%d\n", strncmp( s2 , s1 , 7 ) );

system( "PAUSE" ); return 0;}其執行結果為1-101-1

- 67 -

‧字串搜尋函式

。使用字串搜尋函式時,需將標頭檔 string.h 包含進來─「#include <string.h>」。

。資料型態 size_t:是為了方便跨平台使用而定義的資料型態,有時候等於 unsigned int,

有時候等於 unsigned long。

實例:函式 strcspn 之使用(第一個字串重頭開始數,數到找到第一個屬於第二個字串的子元為止,看之前共數過了多少個字元)

#include <stdio.h>#include <string.h>

int main(){

char *s1 = "The value is 3.14159";char *s2 = "1234567890";

printf( "%u \n", strcspn( s1 , s2 ) );

system( "PAUSE" ); return 0;

- 68 -

}其執行結果為13

‧字串處理記憶體函式

。使用字串處理記憶體函式時,需將標頭檔 string.h 包含進來─「#include <string.h>」。

。除了 mem 使用字串處理記憶體函式時,需將標頭檔 string.h 包含進來─「#include

<string.h>」。

實例 1:函式 memcpy 之使用#include <stdio.h>#include <string.h>

int main(){

char s1[] = "Copy this string"; //含終止字元共有 17 個字元char s2[ 17 ];char s3[ 17 ];

memcpy( s2 , s1 , 17 );printf(“%s\n”, s2 );memcpy( s3 , s1 , 16 );printf(“%s\n”, s3 );

system( "PAUSE" ); return 0;}其執行結果為Copy this stringCopy this stringP#$

- 69 -

實例 2:函式 memmove 與 memset 之使用#include <stdio.h>#include <string.h>

int main(){

char s1[] = "Home Sweet Home";

memmove( s1 , &s1[ 5 ] , 10 );printf(“%s\n”, s1 );

memset( s1 ,‘b’, 4 );printf(“%s\n”, s1 );

system( "PAUSE" ); return 0;}其執行結果為Sweet Home Homebbbbt Home Home

‧其他字串相關函式

。使用以下函式時,需將標頭檔 string.h 包含進來─「#include <string.h>」。

實例 1:函式 strerror 之使用#include <stdio.h>#include <string.h>

int main(){

printf( "%s\n" , strerror( 1 ) );printf( "%s\n" , strerror( 20 ) );printf( "%s\n" , strerror( 40 ) );printf( "%s\n" , strerror( 41 ) );printf( "%s\n" , strerror( 42 ) );printf( "%s\n" , strerror( 43 ) );printf( "%s\n" , strerror( 44 ) );system( "PAUSE" ); return 0;

}其執行結果為Operation not permittedNot a directory

- 70 -

Function not implementedIllegal byte sequenceUnknown errorUnknown error

實例 2:函式 strlen 之使用#include <stdio.h>#include <string.h>

int main(){

printf( "%d\n" , strlen(“four”) );printf( "%d\n" , strlen(“Boston”) );printf( "%d\n" , strlen(“No such file directory”) );system( "PAUSE" ); return 0;

}其執行結果為4622

8.4 字元與字串輸入/輸出函式庫

‧使用以下函式時,需將標頭檔 stdio.h 包含進來─「#include <stdio.h>」。

實例 1:函式 gets 和 putchar 之使用#include <stdio.h>

void reverse( const char *const sPtr );int main(){

char sentence[ 80 ];

- 71 -

printf( "Enter a line of text:\n" );gets( sentence );

reverse( sentence );

system( "PAUSE" ); return 0;}

void reverse( const char *const sPtr ){

if ( sPtr[ 0 ] ==‘\0’)return;

elsereverse( &sPtr[ 1 ] );putchar( sPtr[ 0 ] );

}其執行結果為Enter a line of text:Characters and StringssgnirtS dna sretcarahC

實例 2:函式 sprintf 和 sscanf 之使用#include <stdio.h>

int main(){

char string1[ 80 ];int x = 1234567 , x1 = 0;float y = 3.14159 , y1 = 1.0;

sprintf( string1 , "%6d%6.2f" , x , y );printf( "%s\n" , string1 );

sscanf( string1 , "%d%f" , &x1 , &y1 );printf( “%d%f\n” , x1 , y1 );

system( "PAUSE" ); return 0;}其執行結果為1234567 3.141234567 3.140000

‧ Homework

自我測驗 #8.1-8.4(有解答),

習題 #8.6, 8.14, 8.25, 8.27(以 atof 和 strtod 為例), 8.29, 8.33.

挑戰題 #8.37&8.38

- 1 -

ASCII (美國標準資訊交換碼) Chart

Decimal十進制

Octal八進制

Hexa-decimal十六進制

Cha-Racter字元

0 0 00NUL或 \0

1 1 01 SOH

2 2 02 STX

3 3 03 ETX

4 4 04 EOT

5 5 05 ENQ

6 6 06 ACK

7 7 07 BEL

8 10 08 BS

9 11 09 HT

10 12 0A LF

11 13 0B VT

12 14 0C FF

13 15 0D CR

14 16 0E SO

15 17 0F SI

16 20 10 DLE

17 21 11 DC1

18 22 12 DC2

19 23 13 DC3

20 24 14 DC4

21 25 15 NAK

22 26 16 SYM

23 27 17 ETB

24 30 18 CAN

25 31 19 EM

26 32 1A SUB

27 33 1B ESC

28 34 1C FS

29 35 1D GS

30 36 1E RS

31 37 1F US

32 40 20SP 或空白

33 41 21 !

34 42 22 "

35 43 23 #

36 44 24 $

37 45 25 %

38 46 26 &

39 47 27 '

40 50 28 (

41 51 29 )

42 52 2A *

43 53 2B +

44 54 2C ,

45 55 2D -

46 56 2E .

47 57 2F /

48 60 30 0

49 61 31 1

50 62 32 2

51 63 33 3

52 64 34 4

53 65 35 5

54 66 36 6

55 67 37 7

56 70 38 8

57 71 39 9

58 72 3A :

59 73 3B ;

60 74 3C <

61 75 3D =

62 76 3E >

63 77 3F ?

64 100 40 @

65 101 41 A

66 102 42 B

67 103 43 C

68 104 44 D

69 105 45 E

70 106 46 F

71 107 47 G

72 110 48 H

73 111 49 I

74 112 4A J

75 113 4B K

76 114 4C L

77 115 4D M

78 116 4E N

79 117 4F O

80 120 50 P

81 121 51 Q

82 122 52 R

83 123 53 S

84 124 54 T

85 125 55 U

86 126 56 V

87 127 57 W

88 130 58 X

89 131 59 Y

90 132 5A Z

91 133 5B [

92 134 5C \

93 135 5D ]

94 136 5E ^

95 137 5F _

96 140 60 `

97 141 61 a

98 142 62 b

99 143 63 c

100 144 64 d

101 145 65 e

102 146 66 f

103 147 67 g

104 150 68 h

105 151 69 i

106 152 6A j

107 153 6B k

108 154 6C l

109 155 6D m

110 156 6E n

111 157 6F o

112 160 70 p

113 161 71 q

114 162 72 r

115 163 73 s

116 164 74 t

117 165 75 u

118 166 76 v

119 167 77 w

120 170 78 x

121 171 79 y

122 172 7A z

123 173 7B {

124 174 7C |

125 175 7D }

126 176 7E ~

127 177 7F DEL

- 2 -

第第第 999 章章章 格格格式式式化化化輸輸輸入入入///輸輸輸出出出

9.1 基本概念

‧ 資料流(streams)

。資料流就是由位元組所形成的序列(sequence),所有輸入和輸出都是透過「資料流」來

完成。

。「輸入資料流」是從一項輸入裝置(例如鍵盤、檔案、磁碟機、網路連線等)流向主記憶

體的位元組所形成;「輸出資料流」是從主記憶體流向一項輸出裝置(例如顯示器、檔案、

印表機、磁碟機、網路連線等)的位元組所形成。

。標準輸入輸出資料流分成:

1.標準輸入資料流:通常直接從鍵盤連到程式。

2.標準輸出資料流:通常直接從程式連到螢幕。

3.標準錯誤資料流:通常直接從程式連到螢幕,用以顯示程式執行的錯誤訊息。

作業系統通常允許將這些資料流重新導向到其他的裝置。

9.2 使用 printf 的格式化輸出

‧ 整數(integer)之格式化輸出

- 3 -

‧ 浮點數(floating number)之之格式化輸出

實例:%e, %E, %g, %G 之使用#include <stdio.h>

int main(){

printf( "%f\n", 1234567.89 );printf( "%e\n", 1234567.89 );printf( "%e\n", +1234567.89 );printf( "%e\n", -1234567.89 );printf( "%E\n", 1234567.89 );printf( "%g\n", 1234567.89 );printf( "%G\n", 1234567.89 );system( "PAUSE" ); return 0;

}其執行結果為1234567.8900001.234568e+0061.234568e+006-1.234568e+0061.234568E+0061.23457e+0061.23457E+006

‧字元(character)與字串(string)之之格式化輸出

。字元和字串的轉換指定詞分別為 c 和 s。

。轉換指定詞 c 必須使用 char 為引數(argument)。

。轉換指定詞 s 則使用一個指向 char 的指標為引數。轉換指定詞 s 會不斷顯示字元,直到

遇到終止字元(’\0’)為止。

‧其他的轉換指定詞

- 4 -

printf( “%%” );之執行結果為%

‧欄位寬度(field width)和精準度(precision)之指定

。使用於浮點數時,精準度代表小數點後應該有幾個位數。如果數值的位數小於指定的精

準度,則多出來的位數會補上 0。

。使用於字串時,精準度代表輸出字串長度的上限。

。顯示浮點數時,若指定的精準度小於浮點數的小數位數,則此浮點數就會四捨五入。

實例 1:欄位寬度與對齊方式#include <stdio.h>

int main(){

printf( "%4d\n", 1 );printf( "%4d\n", 12 );printf( "%4d\n", 123 );printf( "%4d\n", 1234 );printf( "%4d\n", 12345 );

printf( "%4d\n", -1 );printf( "%4d\n", -12 );printf( "%4d\n", -123 );printf( "%4d\n", -1234 );printf( "%4d\n", -12345 );

printf( "%4d\n", -1 );system( "PAUSE" ); return 0;

}其執行結果為

112

- 5 -

123123412345

-1-12

-123-1234-12345

實例 2:旗標之使用#include <stdio.h>

int main(){

printf( "% 6d\n", 123 );printf( "%+6d\n", 123 );printf( "%+6d\n", -123 );printf( "%-6d\n", 123 );printf( "%06d\n", 123 );system( "PAUSE" ); return 0;

}其執行結果為

123+123-123

123000123

‧字元常數(character constant)和脫序字元(escape character)的顯示

- 6 -

9.3 使用 scanf 的格式化輸入

實例 1:掃瞄集之使用#include <stdio.h>

int main(){

char z[ 9 ];

printf( "Enter string: " );scanf( "%[aeiou]", z );

system( "PAUSE" ); return 0;}其執行結果為

- 7 -

aaeeiioouuhaaaaeeiioouu

實例 2:跳過輸入「*」之使用#include <stdio.h>

int main(){

int year, month , day;

printf( "Enter a date in the form yyyy-mm-dd: \n" );scanf( "%d%*c%d%*c%d", &year , &month , &day );printf( "%d.%d.%d\n", year , month , day );

system( "PAUSE" ); return 0;}其執行結果為Enter a date in the form yyyy-mm-dd:2008-01-032008.01.03

‧ Homework

自我測驗 #9.1-9.3(有解答),

習題 #9.4, 9.5, 9.6, 9.13.

- 8 -

第第第 111111 章章章 檔檔檔案案案處處處理理理

11.1 基本概念

‧ 資料階層:電腦處理的資料存在於不同的資料階層裡。如圖 11.1,8 個位元(bit)組成一個

ASCII 的字元(character),數個字元(character)組成一個字串(string)或欄位(fields),幾個欄

位再組成一筆記錄(record),多筆記錄最後形成一個檔案(file)。

11.2 循序存取檔案

‧ 有許多方法可以來管理一個檔案(file),最常見的方式為循序存取,以循序存取方式來管理

的檔案稱之為循序檔案(sequential file)。

‧ C 語言將每個檔案視為連續的位元組串流,而且會有一個判斷檔案結尾的方法,例如使用

檔案結尾(end-of-file; EOF)記號。

- 9 -

‧ 當一個檔案被開啟後,就會建立和這個檔案有關的輸入(input)、輸出(output)或錯誤(error)

資料流(streams)。標準函式庫提供了許多函式,用來從檔案讀取資料以及寫入資料到檔

案,例如:

FILE *fPtr; //宣告 fPtr 為一檔案位置指標(file position pointer)char c1;

fPtr = fopen(“file1.dat”,“w”); //開啟一個「僅供寫入」的檔案 file.dat,並將 fPtr 指向該檔案的起始位置

fprintf( fPtr ,“%d”, x1 ); //將整數 x1 的值寫入目前 fPtr 所指的檔案位置

fscanf( fPtr ,“%d”, &x2 ); //從 fPtr 所指的檔案位置讀取一個整數值到 x2

c1 = fgetc( fPtr ); //從 fPtr 所指的檔案位置讀取一個字元並儲存到 c1

fputs( c1 , fPtr); //將 c1 所儲存的字元寫入 fPtr 所指的檔案位置

rewind( fPtr ); //將 fPtr 重新指回到檔案的開頭

fclose( fPtr ); //關閉 fPtr 所指向之檔案

‧ 檔案開啟模式:

。如果想要建立一個新的檔案,或是想要在寫入資料前丟棄原有內容,就以寫入模式“w”

開啟;如果想要讀取一個已經存在的檔案,就以讀取模式“r”開啟;如果想要在一個已經

存在的檔案後面加入新的資料,就要用附加模式“a”開啟。

- 10 -

‧ 以任何模式開啟檔案而發生錯誤時,fopen 函式就會回傳 NULL 值。

實例 1:使用 fopen 和 fclose 函式將九九乘法表寫入檔案#include <stdio.h>

int main(){

int i , j;FILE *fPtr; //宣告一個指向檔案的指標

fPtr = fopen(“file1.dat”,“w”); //開啟一個供「寫入」的檔案 file.dat

for ( i = 1 ; i <= 9 ; i++ ) {for ( j = 1 ; j <= 9 ; j++ )

fprintf( fPtr , "%1d*%1d=%-2d " , i , j , i * j ); //資料寫入該檔案fprintf( fPtr ,“\n” );

}

fclose( fPtr ); //關閉該檔案

system( "PAUSE" ); return 0;}其執行結果為1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7 1*8=8 1*9=92*1=2 2*2=4 2*3=6 2*4=8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=183*1=3 3*2=6 3*3=9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=274*1=4 4*2=8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=365*1=5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=456*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=547*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=638*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=729*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

實例 2:函式 fopen 和 fclose 之使用#include <stdio.h>

int main(){

int account;char name[ 30 ];double balance;FILE *fPtr; //宣告一個指向檔案的指標

if ( ( fPtr = fopen(“clients.dat”,“w”) ) == NULL ) {printf(“File could not be opened\n”);

}else {

printf(“Enter the account, name, and balance.\n”);printf(“Enter <Ctrl>z to end input.\n”);printf(“? ”);

- 11 -

while ( 1 ) {scanf(“%d%s%lf”, &account , name , &balance );if ( feof( stdin ) ) break; //判定是不是從鍵盤輸入了一個檔案結尾(EOF)記號fprintf( fPtr , "%d %s %lf\n" , account , name , balance );printf(“Enter the account, name, and balance.\n”);printf(“Enter <Ctrl>z to end input.\n”);printf(“? ”);

}}

fclose( fPtr );

system( "PAUSE" ); return 0;}其執行結果為Enter the account, name, and balance.Enter <Ctrl>z to end input.? 10 John 5600.00Enter the account, name, and balance.Enter <Ctrl>z to end input.? 20 Marry 10200.00Enter the account, name, and balance.Enter <Ctrl>z to end input.? ^Z開啟檔案 clients.dat,其儲存內容為10 John 5600.00000020 Marry 10200.000000

11.3 隨機存取檔案

‧ 隨機存取檔案中的每一筆記錄通常都有相同的長度,所以程式可以算出每一筆記錄的確切

位置。

‧ 透過隨機存取,程式可以在檔案中加入資料,卻不會破壞其他資料。程式也可以更改或刪

除以前的資料,而不必將整個檔案重新寫入一遍。

‧ 隨機存取檔案內所儲存的資料為未格式化(unformatted),未格式化資料的特性為

1. 相同型別的資料佔用相同的記憶體長度。

2. 同一種記錄的資料佔用相同的記憶體長度。

3. 資料為人無法閱讀(Data not human readable)。

‧ 未格式化輸入/輸出函式(Unformatted Input/Output Functions)

。fwrite 函式:將指定長度的未格式化資料從記憶體寫到檔案。fwrite 會從記憶體的某個

位置,將指定數目的位元組傳送到檔案位置指標所指向的檔案中。

- 12 -

例子: fwrite( &num[0] , sizeof( int ) , 2 , fPtr ); //從以 num[0]為始的記憶體位置,複製 2

個整數的位元組長度至 fPtr 所指的檔案位置

。fread 函式:將指定長度的未格式化資料從檔案寫到記憶體。fread 會從檔案位置指標所

指的位置,將指定數目的位元組傳送到記憶體的某個位置。

例子: fread( &num[0] , sizeof( int ) , 2 , fPtr ); //從 fPtr 所指向的檔案位置,複製 2 個整

數的位元組長度至以 num[0]為始的記憶體位置。

實例:函式 fwrite, fread 與 fseek 之使用

#include <stdio.h>

struct clientData { // 宣告「結構」資料型別 struct clientData 的組成內容int acctNum;char name[ 20 ];double balance;

};

int main(){

int i;FILE *fPtr; //宣告一個指向檔案的指標struct clientData client1 = { 2 ,“John”, 1000. }; //宣告 client1 為 struct clientData

//資料型別,並設定其初始值struct clientData client2 = { 5 ,“Marry”, 5000. }; //宣告 client2 為 struct clientData

//資料型別,並設定其初始值struct clientData client3; //宣告 client3 為 struct clientData 資料型別

fPtr = fopen(“clients.dat”,“rb+”); //開啟一個以二進制處理的可讀寫檔案

/* 將 client1 的資料寫入檔案 clients.dat */fseek( fPtr , (client1.acctNum–1 ) * sizeof( struct clientData ) , SEEK_SET ); //fPtr 定位fwrite( &client1 , sizeof ( struct clientData ) , 1 , fPtr );

/* 將 client2 的資料寫入檔案 clients.dat */fseek( fPtr , (client2.acctNum–1 ) * sizeof( struct clientData ) , SEEK_SET ); //fPtr 定位fwrite( &client2 , sizeof ( struct clientData ) , 1 , fPtr );

/* 從檔案 clients.dat 讀取 client1 的資料並從螢幕輸出 */fseek( fPtr , (client1.acctNum–1 ) * sizeof( struct clientData ) , SEEK_SET ); //fPtr 定位fread( &client3 , sizeof ( struct clientData ) , 1 , fPtr ); //從檔案讀到 client3 裡printf(“%d %s %lf\n”, client3.acctNum , client3.name , client3.balance ); //螢幕顯示

/* 從檔案 clients.dat 讀取 client2 的資料並從螢幕輸出 */fseek( fPtr , (client2.acctNum–1 ) * sizeof( struct clientData ) , SEEK_SET ); //fPtr 定位

- 13 -

fread( &client3 , sizeof ( struct clientData ) , 1 , fPtr ); //從檔案讀到 client3 裡printf(“%d %s %lf\n”, client3.acctNum , client3.name , client3.balance ); //螢幕顯示

fclose( fPtr ); //關閉該檔案

system( "PAUSE" ); return 0;}其執行結果為2 John 1000.0000005 Marry 5000.000000

‧ fseek 之使用:符號常數 SEEK_SET 表示以檔案開頭為起點來計算檔案位置指標的位移量;

符號常數 SEEK_CUR 表示從目前的位置起算;符號常數 SEEK_END 表示從檔案結束的位

置起算。

‧ Homework

自我測驗 #11.1-11.4(有解答),

習題 #11.11, 11.12