C語言入門經典(第5版)

qinghuawenkang發表於2018-10-24

C 語言入門經典
(第 5 版)
[美] Ivor Horton 著
楊 浩 譯
清華大學出版社
北 京

Ivor Horton
Beginning C, Fifth Edition
EISBN
978-1-4302-4881-1
Original English language edition published by Apress Media. Copyright © 2013 by Apress Media.
Simplified Chinese-Language edition copyright © 2013 by Tsinghua University Press. All rights
reserved.
本書中文簡體字版由 Apress 出版公司授權清華大學出版社出版。未經出版者書面許可,不得以任何方
式複製或抄襲本書內容。
北京市版權局著作權合同登記號 圖字: 01-2013-5119
本書封面貼有清華大學出版社防偽標籤,無標籤者不得銷售。
版權所有,侵權必究。侵權舉報電話:
010-62782989 13701121933
圖書在版編目 (CIP) 資料
C 語言入門經典(第 5 版) / (美) 霍爾頓(Horton, I.) 著;楊浩 譯. —北京:清華大學出版社, 2013
(2018.5 重印)
書名原文: Beginning C, Fifth Edition
ISBN 978-7-302-34341-7
Ⅰ. ①C… Ⅱ. ①霍… ②楊… Ⅲ. ①C 語言—程式設計 Ⅳ. ①TP312
中國版本圖書館 CIP 資料核字(2013)第 255156 號
責任編輯:王 軍 於 平
裝幀設計:牛豔敏
責任校對:邱曉玉
責任印製:沈 露
出版發行:清華大學出版社
網 址: ,
地 址:北京清華大學學研大廈 A 座 郵 編: 100084
社 總 機: 010-62770175 郵 購: 010-62786544
投稿與讀者服務: 010-62776969, c-service@tup.tsinghua.edu.cn
質 量 反 饋: 010-62772015, zhiliang@tup.tsinghua.edu.cn
印 刷 者:清華大學印刷廠
裝 訂 者:三河市溧源裝訂廠
經 銷:全國新華書店
開 本: 185mm×260mm 印 張: 37.75 字 數: 872 千字
版 次: 2013 年 11 月第 1 版 印 次: 2018 年 5 月第 18 次印刷
印 數: 62001~67000
定 價: 79.80 元
————————————————————————————————————————————
產品編號: 052455-02

作 者 簡 介
Ivor Horton
原本是一名數學家,因聽聞資訊科技工作輕鬆且收入豐厚而踏足其中。
儘管現實情況常常是工作辛苦且收入相對一般, 但他至今依然堅持從事計算機工作。 Ivor
在不同的時期從事過各種型別的工作,包括程式設計、系統設計、諮詢顧問以及管理和
實現一些頗為複雜的專案。 Ivor 對於將計算機系統設計和實現應用在各種行業工程設計
和運營管理方面有著十分豐富的經驗。他能夠運用多種程式語言開發特定用途的應用程
序,同時還為科研人員和工程人員提供教學,以幫助他們完成這類工作。多年來,他一
直撰寫程式設計方面的書籍,近期作品包括 C、 C++和 Java 教程。在寫書與指導他人之餘,
一般他會選擇釣魚、旅行和享受生活。

技術審稿人簡介
Marc Gregoire
是一名來自比利時的軟體工程師。他畢業於比利時天主教魯汶大
學(Catholic University of Leuven),並擁有該校的計算機工程學碩士學位。一年後,他以
優異成績獲得了同一所大學的人工智慧專業碩士學位。畢業後, Marc 開始在一家名為
Ordina Belgium 的軟體諮詢公司工作。作為一名諮詢師,他主要為西門子及諾基亞西門
子網路公司提供服務, 工作內容包括幫助電信運營商在 Solaris 上執行關鍵性的 2G 與 3G
軟體。這項工作需要身處國際範圍的團隊(跨度從南美地區和美國到歐洲、中東地區、非
洲以及亞洲)中完成。現在, Marc 在 Nikon Metrology 從事 3D 鐳射掃描軟體開發工作。
Marc 在 C/C++方面經驗豐富,尤其精通 Microsoft VC++和 MFC 框架。此外, Marc
也喜愛 C#,並使用 PHP 建立網頁。除了主要對 Windows 開發感興趣之外, Marc 還對開
發 Linux 平臺上 24/7 全天候執行的 C++程式頗有心得(例如, EIB 家庭自動化軟體)。
自 2007 年 4 月起,Marc 就因他在 Visual C++方面的豐富經驗而屢屢榮獲每年的 Microsoft
的年度 MVP(Most Valuable Professional,最有價值專家)獎。
Marc 是比利時 C++使用者群組()的創始人,並經常以 Marc G 的代號活
躍在 CodeGuru 論壇中。他還建立了一些免費軟體和共享軟體,併發布在了他的網站
上。此外, Marc 還在 /blog/上維護自己的部落格。

致 謝
作者只不過是一個大型團隊中將書影印成冊之人。我想感謝整個 Apress 編輯部與產
品組自始至終的幫助與支援。我要感謝 Jonathan Gennick 對於啟動本書新版本所做的努力,
感謝 Jill Balzano 在整個編輯過程中耐心地幫助解決我遇到的各種難題。
我還要感謝我的技術編輯 Marc Gregoire,感謝他幫助稽核文字和檢查所有的程式碼片
段與示例。他找錯的本領實在了得,他的許多建設性的評論與深思熟慮的建議無疑使本
書變成了一本更好的教程。

前 言
歡迎使用《C 語言入門經典(第 5 版)》。研讀本書,你就可以成為一位稱職的 C 語言
程式設計師。從許多方面來說, C 語言都是學習程式設計的理想起步語言。 C 語言很簡潔,
因此無須學習大量的語法便能夠開始編寫真正的應用程式。除了簡明易學以外,它還是
一門功能非常強大的語言,並被專業人士廣泛應用在各種領域。 C 語言的強大之處主要
體現在,它能夠應用於各類層次的開發中,從裝置驅動程式和作業系統元件到大規模應
用程式,它都能勝任。此外, C 語言還可以適用於相對較新的手機應用程式開發上。
幾乎所有計算機都包含 C 語言編譯器,因此,當你學會了 C 語言,就可以在任何環
境下進行程式設計。 最後一點, 掌握 C 語言可以為理解物件導向的 C++語言奠定良好的基礎。
在作者眼中,有抱負的程式設計師必將面對三重障礙,即掌握遍佈程式設計語言中的各
類術語、理解如何使用語言元素(而不僅僅只是知道它們的概念)以及領會如何在實際場
景中應用該語言。本書的目的就是將這些障礙降到最低限度。
術語是專業人士及優秀業餘愛好者之間的交流必不可少的,因此有必要掌握它們。
本書將確保你理解這些術語,並自如地在各種環境下使用它們。這樣才能更有效地使用
大多數軟體產品附帶的文件,且能輕鬆地閱讀和學習大部分程式設計語言相關的著作。
理解語言元素的語法和作用固然是學習 C 語言過程中的一個重要部分,但認識語言
特性如何工作及應用也同等重要。本書不僅採用了程式碼片段,還在每個章節中使用一些
實際應用示例展示語言特性如何應用於特定的問題。這些示例提供了實踐的基礎,讀者
可以透過改動程式碼觀察修改後的結果。
理解特定背景下的程式設計不僅只是應用個別語言元素。為了幫助讀者理解它們,
本書大部分章節之後都給出了一個較為複雜的應用程式,以應用本章之前學到的知識。
這些程式可以幫助你獲得開發應用程式的能力與信心,瞭解如何聯合以及更大範圍地應
用語言元素。最重要的是,它們能讓你瞭解設計實際應用程式與管理實際程式碼會碰到的
問題。
不管學習什麼程式設計語言,有幾件事情都要意識到。首先,雖然要學的東西很多,
但是掌握它們之後,你就會有極大的成就感。其次,學習的過程很有趣,你會深深地體
會到這點;第三,只有透過動手實踐才能學會程式設計,這也是本書貫徹的思想。最後,在
學習的過程中,肯定會時不時犯許多錯誤和感到沮喪。當覺得自己完全停滯時,你要做
的就是堅持。最終你一定會體驗到成功的喜悅,並且回頭想想時,你會覺得它也並沒有
你想象中的那麼難。

如何使用本書
作者認為動手實踐是學習程式設計最好的方法,很快你就會編寫第一個程式了。每一章
都會有幾個將理論應用於實踐的程式,它們也是本書的核心所在。建議讀者手工鍵入並
執行書中的示例,因為手工鍵入可以極大地幫助記憶語言元素。此外,你還應當嘗試解
決每章末尾的所有練習題。當你第一次將一個程式執行成功,尤其是在解決自己的問題
後,你會感覺到很大的成就感和驚人的進步速度,那時你一定會覺得一切都挺值得。
剛開始,學習的進展不會太快,不過隨著逐漸深入,你的學習速度會越來越快。每
一章都會涉及許多基礎知識,因此在學習新的內容之前,需要花些時間確保理解前面學
習過的所有知識。實踐各部分的程式碼,並嘗試實現自己的想法,這是學習程式設計語言
的一個重要部分。嘗試修改書中的程式,看看還能讓它們做些什麼,那才是有趣之處。
不要害怕嘗試,如果某些地方不太明白,嘗試輸入一些變體,看看會出現什麼情況。出
錯並沒什麼大不了,你會從出錯中學到很多知識。一個不錯的方法是徹底通讀每一章,
瞭解各章的範圍,然後回過頭來過一遍所有的示例。
你可能會覺得某些章末尾的練習題非常難。如果第一次沒有完全搞明白,不用擔心。
之所以第一次覺得困難是因為它們通常都是將你所學的知識應用到了相對複雜的問題
中。如果你實在覺得困難的話,可以略過它們繼續學習下一章,然後再回頭研究這些程
序。你甚至可以閱讀完整本書再考慮它們。儘管如此,如果你能完成練習的話,說明你
取得了真正的進步。
本書讀者物件
《C 語言入門經典(第 5 版)》的目的是教會讀者如何儘可能簡單快速地編寫有用的
程式。在閱讀完全書後,讀者會徹底瞭解 C 語言程式設計。這本教程面向的是那些之前編過
一些程式,瞭解背後的概念,並且希望透過學習 C 語言進一步擴充套件知識的讀者。儘管如
此,本書並未假設讀者擁有先前的程式設計知識,因此如果你剛剛接觸程式設計,本書依然是你
的不錯選擇。
使用本書的條件
要使用本書,你需要一臺安裝 C 編譯器和庫的計算機以執行書中的示例,以及一個
程式文字編譯器用於建立原始碼檔案。你使用的編譯器應支援目前 C 語言國際標準
(ISO/IEC 9899:2011,也被稱為 C11)。你還需要一個用於建立和修改程式碼的編輯器,可以
採用純文字編輯器(如記事本(Notepad)或 vi)建立原始檔。不過,採用專為編輯 C 語言代
碼設計的編輯器會更有幫助。
以下是作者推薦的兩款 C 語言編譯器,均為免費軟體:
● GNU C 編譯器, GCC,可從 下載,它支援多種不同的作業系統
環境。

● 面向 Microsoft Windows 的 Pelles C 編譯器,可從
pellesc/下載,它提供了一個非常棒的整合開發環境(IDE)。
本書採用的約定
本書的文字和佈局採用了許多不同的樣式,以便區分各種不同的資訊。大多數樣式
表達的含義都很明顯。程式程式碼樣式如下:
int main(void)
{ printf("Beginning C\n");
return 0;
}
如果程式碼片段是從前面的例項修改而來,修改過的程式碼行就用粗體顯示,如下所示:
i int main(void)
{
printf("Beginning C by Ivor Horton\n");
return 0;
}
當程式碼出現在文字中時,它的樣式會有所不同,如: double
程式程式碼中還是用了各種“括號”。它們之間的差別非常重要,不同稱呼。本書中稱
()為圓括號, {}為大括號, []為方括號。

目 錄
1 C 語言程式設計 .............................1
1.1 C 語言 .........................................1
1.2 標準庫.........................................2
1.3 學習 C .........................................2
1.4 建立 C 程式 ................................2
1.4.1 編輯 ........................................ 2
1.4.2 編譯 ........................................ 3
1.4.3 連結 ........................................ 4
1.4.4 執行 ........................................ 4
1.5 建立第一個程式.........................5
1.6 編輯第一個程式.........................5
1.7 處理錯誤.....................................6
1.8 剖析一個簡單的程式.................7
1.8.1 註釋 ........................................ 7
1.8.2 預處理指令 ............................ 8
1.8.3 定義 main()函式..................... 9
1.8.4 關鍵字 .................................. 10
1.8.5 函式體 .................................. 10
1.8.6 輸出資訊 .............................. 11
1.8.7 引數 ...................................... 11
1.8.8 控制符 .................................. 11
1.8.9 三字母序列 .......................... 13
1.9 前處理器...................................14
1.10 用 C 語言開發程式 ................14
1.10.1 瞭解問題 ............................ 14
1.10.2 詳細設計 ............................ 15
1.10.3 實施 .................................... 15
1.10.4 測試 .................................... 15
1.11 函式及模組化程式設計 .................16
1.12 常見錯誤.................................19
1.13 要點.........................................19
1.14 小結.........................................20
1.15 習題.........................................20
2 章 程式設計初步 .............................. 21
2.1 計算機的記憶體 ...........................21
2.2 什麼是變數 ...............................23
2.3 儲存整數的變數 .......................24
2.3.1 變數的使用 .......................... 28
2.3.2 變數的初始化 ...................... 29
2.4 變數與記憶體 ...............................36
2.4.1 帶符號的整數型別 .............. 36
2.4.2 無符號的整數型別 .............. 37
2.4.3 指定整數常量 ...................... 37
2.5 使用浮點數 ...............................39
2.6 浮點數變數 ...............................41
2.6.1 使用浮點數完成除法
運算 ......................................
42
2.6.2 控制輸出中的小數位數 ...... 43
2.6.3 控制輸出的欄位寬度 .......... 43
2.7 較複雜的表示式 .......................44
2.8 定義命名常量 ...........................46
2.8.1 極限值 .................................. 49
2.8.2 sizeof 運算子........................ 51
2.9 選擇正確的型別 .......................52
2.10 強制型別轉換 .........................55
2.10.1 自動轉換型別 .................... 56
2.10.2 隱式型別轉換的規則 ........ 56
2.10.3 賦值語句中的隱式型別
轉換 ......................................
57
2.11 再談數值資料型別 .................58
2.11.1 字元型別 ............................ 58
2.11.2 字元的輸入輸出 ................ 59
2.11.3 列舉 .................................... 62
2.11.4 儲存布林值的變數 ............ 64
2.12 賦值操作的 op=形式..............65
2.13 數學函式 .................................66

2.14 設計一個程式.........................67
2.14.1 問題 .................................. 68
2.14.2 分析 .................................. 68
2.14.3 解決方案 .......................... 70
2.15 小結.........................................73
2.16 練習.........................................74
第 3 章 條件判斷...............................75
3.1 判斷過程...................................75
3.1.1 算術比較 .............................. 75
3.1.2 基本的 if 語句 ...................... 76
3.1.3 擴充套件 if 語句: if-else ............ 79
3.1.4 在 if 語句中使用程式碼塊 ...... 82
3.1.5 巢狀的 if 語句 ...................... 83
3.1.6 測試字元 .............................. 85
3.1.7 邏輯運算子 .......................... 88
3.1.8 條件運算子 .......................... 91
3.1.9 運算子的優先順序................... 94
3.2 多項選擇問題...........................98
3.2.1 給多項選擇使用 else-if
語句 ......................................
98
3.2.2 switch 語句 ........................... 99
3.2.3 goto 語句 ........................... 107
3.3 按位運算子.............................108
3.3.1 按位運算子的 op=用法 ..... 110
3.3.2 使用按位運算子................. 111
3.4 設計程式................................. 114
3.4.1 問題 .................................... 114
3.4.2 分析 .................................... 114
3.4.3 解決方案 ............................ 114
3.5 小結......................................... 118
3.6 練習......................................... 118
第 4 章 迴圈.................................... 119
4.1 迴圈......................................... 119
4.2 遞增和遞減運算子.................120
4.3 for 迴圈 ...................................120
4.4 for 迴圈的一般語法 ...............124
4.5 再談遞增和遞減運算子.........125
4.5.1 遞增運算子 ........................ 125
4.5.2 遞增運算子的前置和後置
形式 ....................................
125
4.5.3 遞減運算子 ........................ 126
4.6 再論 for 迴圈 ..........................127
4.6.1 修改 for 迴圈變數.............. 129
4.6.2 沒有引數的 for 迴圈.......... 129
4.6.3 迴圈內的 break 語句.......... 130
4.6.4 使用 for 迴圈限制輸入...... 132
4.6.5 生成偽隨機整數 ................ 135
4.6.6 再談迴圈控制選項 ............ 137
4.6.7 浮點型別的迴圈控制
變數 ....................................
137
4.7 while 迴圈 ...............................138
4.8 巢狀迴圈 .................................140
4.9 巢狀迴圈和 goto 語句 ............146
4.10 do-while 迴圈........................147
4.11 continue 語句.........................149
4.12 設計程式 ...............................150
4.12.1 問題 ............................... 150
4.12.2 分析 ............................... 150
4.12.3 解決方案 ....................... 151
4.13 小結 .......................................162
4.14 習題 .......................................163
第 5 章 陣列 ................................... 165
5.1 陣列簡介 .................................165
5.1.1 不用陣列的程式 ................ 165
5.1.2 什麼是陣列 ........................ 167
5.1.3 使用陣列 ............................ 168
5.2 定址運算子 .............................171
5.3 陣列和地址 .............................173
5.4 陣列的初始化 .........................174
5.5 確定陣列的大小 .....................175
5.6 多維陣列 .................................176
5.7 多維陣列的初始化 .................178
5.8 變長陣列 .................................184
5.9 設計一個程式 .........................186

5.9.1 問題 ...................................
186
5.9.2 分析 ................................... 186
5.9.3 解決方案 ........................... 187
5.10 小結.......................................193
5.11 習題.......................................193
第 6 章 字串和文字的應用...........195
6.1 什麼是字串.........................195
6.2 儲存字串的變數.................197
字串陣列 ................................... 199
6.3 字串操作.............................202
6.3.1 檢查對 C11 的支援 ........... 202
6.3.2 確定字串的長度............ 203
6.3.3 複製字串 ....................... 204
6.3.4 連線字串 ....................... 204
6.3.5 比較字串 ....................... 208
6.3.6 搜尋字串 ........................ 211
6.3.7 單元化字串.................... 215
6.3.8 將換行符讀入字串........ 219
6.4 分析和轉換字串.................221
6.4.1 轉換字元的大小寫形式.... 223
6.4.2 將字串轉換成數值........ 225
6.5 設計一個程式.........................227
6.5.1 問題 ................................... 227
6.5.2 分析 ................................... 227
6.5.3 解決方案 ........................... 228
6.6 小結.........................................233
6.7 習題.........................................233
第 7 章 指標....................................235
7.1 指標初探.................................235
7.1.1 宣告指標 ........................... 236
7.1.2 透過指標訪問值................ 237
7.1.3 使用指標 ........................... 240
7.1.4 指向常量的指標................ 244
7.1.5 常量指標 ........................... 244
7.1.6 指標的命名 ....................... 245
7.2 陣列和指標.............................245
7.3 多維陣列.................................248
7.3.1 多維陣列和指標 ................ 252
7.3.2 訪問陣列元素 .................... 253
7.4 記憶體的使用 .............................256
7.4.1 動態記憶體分配: malloc()
函式 ....................................
256
7.4.2 釋放動態分配的記憶體 ........ 257
7.4.3 用 calloc()函式分配
記憶體 ....................................
261
7.4.4 擴充套件動態分配的記憶體 ........ 262
7.5 使用指標處理字串 .............265
7.5.1 使用指標陣列 .................... 266
7.5.2 指標和陣列記號 ................ 272
7.6 設計程式 .................................276
7.6.1 問題 .................................... 276
7.6.2 分析 .................................... 277
7.6.3 解決方案 ............................ 277
7.7 小結 .........................................284
7.8 習題 .........................................285
第 8 章 程式設計的結構 ........................ 287
8.1 程式的結構 .............................287
8.1.1 變數的作用域和生存期 .... 288
8.1.2 變數的作用域和函式 ........ 291
8.2 函式 .........................................291
8.2.1 定義函式 ............................ 291
8.2.2 return 語句.......................... 294
8.3 按值傳遞機制 .........................299
8.4 函式原型 .................................300
8.5 指標用作引數和返回值 .........301
8.5.1 常量引數 ............................ 302
8.5.2 返回指標的風險 ................ 307
8.6 小結 .........................................310
8.7 習題 .........................................310
第 9 章 函式再探 ............................ 313
9.1 函式指標 .................................313
9.1.1 宣告函式指標 .................... 313
9.1.2 透過函式指標呼叫函式 .... 314
9.1.3 函式指標的陣列 ................ 316
9.1.4 作為變元的函式指標........ 319
9.2 函式中的變數.........................321
9.2.1 靜態變數:函式內部
的追蹤 ...............................
321
9.2.2 在函式之間共享變數........ 323
9.3 呼叫自己的函式:遞迴.........325
9.4 變元個數可變的函式.............328
9.4.1 複製 va_list........................ 331
9.4.2 長度可變的變元列表
的基本規則 .......................
331
9.5 main()函式 ..............................332
9.6 結束程式.................................333
9.6.1 abort()函式......................... 333
9.6.2 exit()和 atexit()函式 .......... 333
9.6.3 _Exit()函式 ........................ 334
9.6.4 quick_exit()和 at_quick_exit()
函式 ...................................
334
9.7 提高效能.................................335
9.7.1 內聯宣告函式.................... 335
9.7.2 使用 restrict 關鍵字........... 335
9.7.3 _Noreturn 函式限定符 ...... 336
9.8 設計程式.................................336
9.8.1 問題 ................................... 336
9.8.2 分析 ................................... 337
9.8.3 解決方案 ........................... 338
9.9 小結.........................................351
9.10 習題.......................................352
第 10 章 基本輸入和輸出操作.........353
10.1 輸入和輸出流.......................353
10.2 標準流...................................354
10.3 鍵盤輸入...............................354
10.3.1 格式化鍵盤輸入 ........... 355
10.3.2 輸入格式控制字串 ... 355
10.3.3 輸入格式字串中
的字元 ...........................
360
10.3.4 輸入浮點數的各種
變化 ...............................
362
10.3.5 讀取十六進位制和八進
制值................................
363
10.3.6 用 scanf_s()讀取字元 .... 364
10.3.7 從鍵盤上輸入字串 .... 366
10.3.8 單個字元的鍵盤輸入 .... 367
10.4 螢幕輸出 ...............................372
10.4.1 使用 printf_s()的格式化
輸出................................
372
10.4.2 轉義序列........................ 375
10.4.3 整數輸出........................ 375
10.4.4 輸出浮點數.................... 378
10.4.5 字元輸出........................ 379
10.5 其他輸出函式 .......................380
10.5.1 螢幕的非格式化輸出 .... 381
10.5.2 陣列的格式化輸出 ........ 381
10.5.3 陣列的格式化輸入 ........ 382
10.6 小結 .......................................382
10.7 習題 .......................................383
第 11 章 結構化資料....................... 385
11.1 資料結構:使用 struct..........385
11.1.1 定義結構型別和結構
變數................................
387
11.1.2 訪問結構成員 ................ 388
11.1.3 未命名的結構 ................ 390
11.1.4 結構陣列 ........................ 391
11.1.5 表示式中的結構成員 .... 393
11.1.6 結構指標 ........................ 393
11.1.7 為結構動態分配記憶體 .... 394
11.2 再探結構成員 .......................397
11.2.1 將一個結構作為另一個
結構的成員....................
397
11.2.2 宣告結構中的結構 ........ 398
11.2.3 將結構指標用作結構
成員................................
399
11.2.4 雙向連結串列 ........................ 403
11.2.5 結構中的位欄位 ............ 406
11.3 結構與函式 ...........................407

11.3.1 結構作為函式的變元....
407
11.3.2 結構指標作為函式
變元 ...............................
408
11.3.3 作為函式返回值的
結構 ...............................
409
11.3.4 二叉樹 ........................... 414
11.4 共享記憶體...............................421
11.5 設計程式...............................425
11.5.1 問題 ............................... 425
11.5.2 分析 ............................... 426
11.5.3 解決方案 ....................... 426
11.6 小結.......................................438
11.7 習題.......................................438
第 12 章 處理檔案...........................441
12.1 檔案的概念...........................441
12.1.1 檔案中的位置 ............... 442
12.1.2 檔案流 ........................... 442
12.2 檔案訪問...............................442
12.2.1 開啟檔案 ....................... 443
12.2.2 快取檔案操作 ............... 445
12.2.3 檔案重新命名 ................... 446
12.2.4 關閉檔案 ....................... 447
12.2.5 刪除檔案 ....................... 447
12.3 寫入文字檔案.......................448
12.4 讀取文字檔案.......................449
12.5 在文字檔案中讀寫
字串 ..................................452
12.6 格式化檔案的輸入輸出.......456
12.6.1 格式化檔案輸出 ........... 456
12.6.2 格式化檔案輸入 ........... 457
12.7 錯誤處理...............................459
12.8 再探文字檔案操作模式.......460
12.9 freopen_s()函式 ....................461
12.10 二進位制檔案的輸入輸出.....462
12.10.1 以二進位制模式開啟
檔案...........................
462
12.10.2 寫入二進位制檔案 ....... 463
12.10.3 讀取二進位制檔案........ 464
12.11 在檔案中移動 .....................469
12.11.1 檔案定位操作............ 469
12.11.2 找出我們在檔案中
的位置........................
470
12.11.3 在檔案中設定位置.... 471
12.12 使用臨時檔案 .....................477
12.12.1 建立臨時檔案............ 477
12.12.2 建立唯一的檔名.... 478
12.13 更新二進位制檔案 .................479
12.13.1 修改檔案的內容........ 484
12.13.2 從鍵盤輸入建立
記錄 ...........................
485
12.13.3 將記錄寫入檔案........ 486
12.13.4 從檔案中讀取記錄.... 486
12.13.5 寫入檔案.................... 487
12.13.6 列出檔案內容............ 488
12.13.7 更新已有的檔案
內容 ...........................
489
12.14 檔案開啟模式小結 .............495
12.15 設計程式 .............................496
12.15.1 問題 ........................... 496
12.15.2 分析 ........................... 496
12.15.3 解決方案.................... 496
12.16 小結 .....................................501
12.17 習題 .....................................501
第 13 章 支援功能 .......................... 503
13.1 預處理 ...................................503
13.1.1 在程式中包含標頭檔案 .... 503
13.1.2 定義自己的標頭檔案 ........ 504
13.1.3 管理多個原始檔 ............ 504
13.1.4 外部變數........................ 505
13.1.5 靜態函式........................ 505
13.1.6 替換程式原始碼 ............ 506
13.2 宏 ...........................................507
13.2.1 看起來像函式的宏 ........ 507
13.2.2 字串作為宏引數 ........ 509

13.2.3 在宏展開式中結合兩個
變元 ...............................
510
13.3 多行上的前處理器指令.......510
13.3.1 前處理器邏輯指令 ........ 511
13.3.2 條件編譯 ........................ 511
13.3.3 測試多個條件 ............... 512
13.3.4 取消定義的識別符號 ....... 512
13.3.5 測試識別符號的指定值
的指令 ...........................
512
13.3.6 多項選擇 ....................... 513
13.3.7 標準預處理宏 ............... 514
13.4 除錯方法...............................515
13.4.1 整合的偵錯程式 ............... 515
13.4.2 除錯階段的前處理器 ... 515
13.4.3 斷言 ............................... 519
13.5 日期和時間函式...................521
13.5.1 獲取時間值 ................... 522
13.5.2 獲取日期 ....................... 525
13.5.3 確定某一天是星期幾 ... 529
13.6 小結.......................................531
13.7 習題.......................................531
第 14 章 高階專用主題 ...................533
14.1 使用國際字符集...................533
14.1.1 理解 Unicode................. 533
14.1.2 設定區域 ....................... 534
14.1.3 寬字元型別 wchar_t ..... 535
14.1.4 寬字串的操作 ........... 537
14.1.5 寬字元的檔案流操作 .... 540
14.1.6 儲存 Unicode 字元的
固定大小型別................
541
14.2 用於可移植性的專用整數
型別 ......................................545
14.2.1 固定寬度的整型 ............ 545
14.2.2 最小寬度的整型 ............ 545
14.2.3 最大寬度的整型 ............ 546
14.3 複數型別 ...............................546
14.3.1 複數基礎........................ 546
14.3.2 複數型別和操作 ............ 547
14.4 用執行緒程式設計 ...........................550
14.4.1 建立執行緒........................ 550
14.4.2 退出執行緒........................ 551
14.4.3 把一個執行緒連線到另一個
執行緒上............................
552
14.4.4 掛起執行緒........................ 555
14.4.5 管理執行緒對資料
的訪問............................
555
14.5 小結 .......................................561
附錄 A 計算機中的數學知識........... 563
附錄 B ASCII 字元程式碼定義 ........... 571
附錄 C C 語言中的保留字 .............. 575
附錄 D 輸入輸出格式指定符 .......... 577
附錄 E 標準庫標頭檔案 ..................... 583


第 1 章
C 語言程式設計
C 語言是一種功能強大、簡潔的計算機語言,透過它可以編寫程式,指揮計算機完
成指定的任務。我們可以利用 C 語言建立程式(即一組指令),並讓計算機依指令行事。
用 C 語言程式設計並不難, 本書將用淺顯易懂的方法介紹 C 語言的基礎知識, 讀完本章,
讀者就可以編寫第一個 C 語言程式了,其實 C 語言很簡單。
本章的主要內容:
● C 語言標準
● 標準庫的概念
● 如何建立 C 程式
● 如何組織 C 程式
● 如何編寫在螢幕上顯示文字的程式
1.1 C 語言
C 是相當靈活的,用於執行計算機程式能完成的幾乎所有任務,包括會計應用程式、
字處理程式、遊戲、作業系統等。它不僅是更高階語言(如 C++)的基礎,目前還以 Objective
C 的形式開發手機應用程式。 Objective C 是標準的 C 加上一小部分物件導向程式設計功能。
C 很容易學習,因為它很簡潔。因此,如果你立志成為一名程式設計師,最好從 C 語言開始
學起,能快速而方便地獲得編寫實際應用程式的足夠知識。
C 語言由一個國際標準定義,目前,其最新版本由 ISO/IEC 9899:2011 文件定義。當
前的標準一般稱為 C11,本書介紹的語言遵循 C11 標準。但要知道, C11 定義的一些語
言元素是可選的。這表示, 遵循 C11 標準的 C 編譯器可能沒有實現該標準中的所有功能。
(編譯器只是一個程式, 它可以把用我們能理解的術語所編寫的程式轉換為計算機能理解
的術語)。本書會標識出 C11 中的可選語言特性,這樣讀者就知道,自己的編譯器可能不
支援它。
C11 編譯器還有可能沒有實現 C11 標準強制的所有語言特性。實現新語言功能是需
要時間的,所以編譯器開發人員常常採用逐步接近的方式實現它們。這也是程式可能不
工作的另一個原因。儘管如此,根據我的經驗, C 程式不能工作的最常見原因,至少有
99.9%的可能性是出現了錯誤。

1.2 標準庫
C 的標準庫也在 C11 標準中指定。標準庫定義了編寫 C 程式時常常需要的常量、符
號和函式。它還提供了基本 C 語言的一些可選擴充套件。取決於機器的特性,例如計算機的
輸入輸出,由標準庫以不依賴機器的形式實現。這意味著,在 PC 中用 C 程式碼把資料寫
入磁碟檔案的方式,與在其他計算機上相同,儘管底層的硬體處理相當不同。庫提供的
標準功能包括大多數程式設計師都可能需要的功能,例如處理文字字串或數學計算,這樣
就免除了自己實現這些功能所需的大量精力。
標準庫在一系列標準檔案——標頭檔案中指定。標頭檔案的副檔名總是.h。為了使一組標
準功能可用於 C 程式檔案,只需要將對應的標準標頭檔案包含進來,其方式在本章後面介
紹。我們編寫的每個程式都會用到標準庫。附錄 E 彙總了構成標準庫的標頭檔案。
1.3 學習 C
如果對程式設計非常陌生,則不需要學習 C 的某些方面,至少在剛開始時不需要學習。
這些功能比較特殊,或者不大常用。本書把它們放在第 14 章,這裡讀者可以在熟悉其他
內容後,再學習它們。
所有示例的程式碼都可以從 Apress 網站()上下載,但建議讀者自
己輸入本書中的所有示例,即使它們非常簡單,也要輸入。自己親自輸入,以後就不容
易忘記。不要害怕用程式碼進行實驗。犯錯對程式設計而言非常有教育性。早期犯的錯誤越多,
學到的東西就越多。
1.4 建立 C 程式
C 程式的建立過程有 4 個基本步驟或過程:
● 編輯
● 編譯
● 連結
● 執行
這些過程很容易完成(就像翻轉手掌一樣簡單,而且可以隨時翻轉),首先介紹每個
過程,以及它們對建立 C 程式的作用。
1.4.1 編輯
編輯過程就是建立和修改 C 程式的原始碼——我們編寫的程式指令稱為原始碼。有
些 C 編譯器帶一個編輯器,可幫助管理程式。通常,編輯器是提供了編寫、管理、開發
與測試程式的環境,有時也稱為整合開發環境(Integrated Development Environment, IDE)。

也可以用一般的文字編輯器來建立原始檔,但它們必須將程式碼儲存為純文字,而沒
有嵌入附加的格式化資料。不要使用字處理器(例如微軟的 Word),字處理器不適合編寫
程式程式碼,因為它們在儲存文字時,會附加一些格式化資訊。一般來說,如果編譯器系
統帶有編輯器,就會提供很多更便於編寫及組織程式的功能。它們通常會自動編排程式
文字的格式,並將重要的語言元素以高亮顏色顯示,這樣不僅讓程式容易閱讀,還容易
找到單詞輸入錯誤。
在 Linux 上,最常用的文字編輯器是 Vim 編輯器,也可以使用 GNU Emacs 編輯器。
對於 Microsoft Windows,可以使用許多免費(freeware)或共享(shareware)的程式設計編輯
器。這些軟體提供了許多功能,例如,高亮顯示特殊的語法及程式碼自動縮排等功能,幫
助確保程式碼是正確的。 Emacs 編輯器也有 Microsoft Windows 版本。 UNIX 環境的 Vi 和
VIM 編輯器也可用於 Windows,甚至可以使用 Notepad++()。
當然,也可以購買支援 C 語言的專業程式設計開發環境,例如微軟或 Borland 的相關產
品,它們能大大提高程式碼編輯能力。不過,在付款之前,最好檢查一下它們支援的 C 級
別是否符合當前的 C 語言標準 C11。因為現在很多編輯器產品主要面向 C++開發人員,
C 語言只是一個次要目標。
1.4.2 編譯
編譯器可以將原始碼轉換成機器語言,在編譯的過程中,會找出並報告錯誤。這個
階段的輸入是在編輯期間產生的檔案,常稱為原始檔。
編譯器能找出程式中很多無效或無法識別的錯誤,以及結構錯誤,例如程式的某部
分永遠不會執行。編譯器的輸出結果稱為物件程式碼(object code),存放它們的檔案稱為對
象檔案(object file),這些檔案的副檔名在 Microsoft Windows 環境中通常是.obj,在
Linux/UNIX 環境中通常是.o。編譯器可以在轉換過程中找出幾種不同型別的錯誤,它們
大都會阻止物件檔案的建立。
如果編譯成功,就會生成一個檔案,它與原始檔同名,但副檔名是.o 或者.obj。
如果在 UNIX 系統下工作,在命令列上編譯 C 程式的標準命令是 cc(若編譯器是
GNU’s Not UNIX(GNU),則命令為.gcc)。下面是一個示例:
cc -c myprog.c
其中, myporg.c 是要編譯的程式,如果省略了–c 這個引數,程式還會自動連結。
成功編譯的結果是生成一個物件檔案。
大多數 C 編譯器都有標準的編譯選項,在命令列(如 cc myprog.c)或整合開發環境下
的選單選項(Compile 選單選項)裡都可找到。在 IDE 中編譯常常比使用命令列容易得多。
編譯過程包括兩個階段。第一個階段稱為預處理階段,在此期間會修改或新增程式碼,
第二個階段是生成物件程式碼的實際編譯過程。原始檔可以包含預處理宏,它們用於新增
或修改 C 程式語句。如果現在不理解它們,不必擔心,本書後面將進行詳細論述。

1.4.3 連結
連結器(linker)將原始碼檔案中由編譯器產生的各種物件模組組合起來,再從 C 語言
提供的程式庫中新增必要的程式碼模組,將它們組合成一個可執行的檔案。連結器也可以
檢測和報告錯誤,例如,遺漏了程式的某個部分,或者引用了一個根本不存在的庫元件。
實際上,如果程式太大,可將其拆成幾個原始碼檔案,再用連結器連線起來。因為
很難一次編寫一個很大的程式,也不可能只使用一個檔案。如果將它拆成多個小原始檔,
每個原始檔提供程式的一部分功能,程式的開發就容易多了。這些原始檔可以分別編譯,
更容易避免簡單輸入錯誤的發生。再者,整個程式可以一點一點地開發,組成程式的源
檔案通常會用同一個專案名稱整合,這個專案名稱用於引用整個程式。
程式庫提供的例程可以執行非 C 語言的操作,從而支援和擴充套件了 C 語言。例如,庫
中包含的例程支援輸入、輸出、計算平方根、比較兩個字串,或讀取日期和時間資訊
等操作。
連結階段出現錯誤,意味著必須重新編輯原始碼;反過來,如果連結成功,就會產
生一個可執行檔案,但這並不一定表示程式能正常工作。在 Microsoft Windows 環境下,
這個可執行檔案的副檔名為.exe;在 UNIX 環境下,沒有副檔名,但它是一個可執行的
檔案型別。多數 IDE 也有 Build(建立)選項,它可一次完成程式的編譯和連結。
1.4.4 執行
執行階段就是當成功完成了前述 3 個
過程後,執行程式。但是,這個階段可能
會出現各種錯誤,包括輸出錯誤及什麼也
不做,甚至使計算機崩潰。不管出現哪種
情況,都必須返回編輯階段,檢查並修改
原始碼。
在這個階段,計算機最終會精確地執
行指令。在 UNIX 和 Linux 下,只要鍵入編
譯和連結後的檔名,即可執行程式。在
大多數 IDE 中,都有一個相應的選單命令
來執行或者執行已編譯的程式。這個 Run
命令或者 Execute 命令可能有自己的選單,
也可能位於 Compile 選單項下。在 Windows
環境中,執行程式的.exe 檔案即可,這與運
行其他可執行程式一樣。
在任何環境及任何語言中,開發程式
的編輯、編譯、連結與執行這 4 個步驟都
是一樣的。圖 1-1 總結了建立 C 程式的各
個過程。
編輯
建立/修改
程式原始碼
原始檔
物件檔案
可執行檔案
成功!
是 錯誤?

執行
執行程式
是 錯誤?

錯誤?
連結
連結庫等
編譯
生成機器指令
否 否

圖 1-1 建立和執行程式

1.5 建立第一個程式
本節先瀏覽一下建立 C 語言程式的流程,從輸入程式碼到執行程式的所有 4 個步驟。
在這個階段,若不瞭解所鍵入的程式碼資訊,別擔心,筆者會解釋每一個步驟。
試試看:
C 程式示例
開啟編輯器,輸入下面的程式,請注意標點符號不要輸錯,第 4 行及最後一行的括
號是大括號{},而不是方括號[]或者圓括號()——這很重要。另外一定要鍵入斜槓(/),以
後也會用到反斜槓(\)。最後別忘了行末的分號(;)。
/* Program 1.1 Your Very First C Program - Displaying Hello World */
#include <stdio.h>
int main(void)
{
printf("Hello world! ");
return 0;
}
在輸入了上面的原始碼後,將程式儲存為 hello.c。可以用任意名字替代 hello,但擴
展名必須是.c。這個副檔名在編寫 C 程式時是一個通用約定,它表示檔案的內容是 C 語
言原始碼。大多數 C 編譯器都要求原始檔的副檔名是.c,否則編譯器會拒絕處理它。
下面編譯程式(如本章前面“編譯”一節所述),連結所有必要的內容,建立一個可
執行程式(如本章前面“連結”一節所述)。編譯和連結一般在一個操作中完成,通常稱
為“構建操作”。原始碼編譯成功後,連結器就新增程式需要的標準庫程式碼,為程式建立
一個可執行檔案。
最後,執行程式。這有幾種方式,在 Windows 環境下,一般只需要在 Windows Explorer
中雙擊.exe 檔案,但最好開啟一個命令列視窗,輸入執行它的命令,因為在程式執行完
畢後,顯示輸出的視窗就會消失。在所有的作業系統環境上,都可以從命令列上執行程
序。只需要啟動一個命令列會話,把當前目錄改為包含程式可執行檔案的目錄,再輸入
程式名,就可以執行它了。
如果沒有出現錯誤,就大功告成了。這個程式會在螢幕上輸出如下資訊:
Hello world!
1.6 編輯第一個程式
我們可以修改程式,在螢幕上輸出其他資訊,例如可以將程式改成:
/*Program 1.2 Your Second C Program */
#include <stdio.h>
int main(void)

{
printf("\"If at first you don't succeed, try, try, try again!\"");
return 0;
}
這個版本的輸出是:
"If at first you don't succeed try try try again! "
在要顯示的文字中, \”序列稱為轉義序列(escape sequence)。文字中包含幾個不同的
轉義序列。 \”是在文字中包含雙引號的特殊方式,因為雙引號通常表示字串的開頭和
結尾。轉義序列\”使雙引號出現在輸出的開頭和結尾。如果不使用轉義序列,不僅雙引
號不會出現在輸出中,而且程式不會編譯。本章後面的“控制字元”一節將詳細介紹轉
義序列。
修改完原始碼後,可以重新編譯,連結後執行。反覆練習,熟悉整個流程。
1.7 處理錯誤
犯錯乃人之常情,沒什麼難為情的。幸好計算機一般不會出錯,而且非常擅長於找
出我們犯的錯誤。編譯器會列出在原始碼中找到的一組錯誤資訊(甚至比我們想象的多),
通常會指出有錯誤的語句。此時,我們必須返回編輯階段,找出有錯誤的程式碼並更正。
有時一個錯誤會使後面本來正確的語句也出現錯誤。這多半是程式的其他部分引用
了錯誤語句定義的內容所造成的。當然,定義語句有錯,但被定義的內容不一定有錯。
下面看看原始碼在程式中生成了一個錯誤時,會是什麼樣的情況。編輯第二個程式
示例,將 printf()行最後的分號去掉,如下所示:
/*Program 1.2 Your Second C Program */
#include <stdio.h)
int main(void)
{
printf("\"If at first you don't succeed, try, try, try again!\"")
return 0;
}
編譯這個程式後,會看到錯誤資訊,具體資訊隨編譯器的不同而略有區別。下面是
一個比較常見的錯誤資訊:
Syntax error : missing ';' before '}'
HELLO.C - 1 error(s), 0 warning(s)
編譯器能精確地指出錯誤及其出處,在這裡, printf()行的結尾處需要一個分號。在
開始編寫程式時,可能有很多錯誤是簡單的拼寫錯誤造成的。還很容易忘了逗號、括號,
或按錯了鍵。沒關係,許多有經驗的老手也常犯這種錯誤。
如前所述,有時一點小錯誤會造成大災難,編譯器會顯示許多不同的錯誤資訊。不
要被錯誤的數量嚇倒,仔細看過每一個錯誤資訊後,返回並改掉錯誤部分,不懂的先不

管它,然後再編譯一次原始檔,就會發現錯誤一次比一次少。
返回編輯器,重新輸入分號,再編譯,看看有沒有其他錯誤,如果沒有錯誤,程式
就可以執行了。
1.8 剖析一個簡單的程式
編寫並編譯了第一個程式後,下面是另一個非常類似的例子,瞭解各行程式碼的作用:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
#include <stdio.h>
int main(void)
{
printf("Beware the Ides Of March!")

return 0;
}
這和第一個程式完全相同,這裡把它作為練習,用編輯器輸入這個示例,編譯並執
行。若輸入完全正確,會看到如下輸出:
Beware the Ides Of March!
1.8.1 註釋
上述示例的第一行程式碼如下:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
這不是程式程式碼,因為它沒有告訴電腦執行操作,它只是一個註釋,告訴閱讀程式碼
的人,這個程式要做什麼。位於/*和*/之間的任意文字都是註釋。只要編譯器在原始檔中
找到/*,就忽略它後面的內容(即使其中的文字很像程式程式碼),一直到表示註釋結束的*/
為止。 /*可以和*/放在同一行程式碼上,也可以放在不同的程式碼行上。如果忘記包含對應的
*/,編譯器就會忽略/*後面的所有內容。下面使用一個註釋說明程式碼的作者及版權所有:
/*
* Written by Ivor Horton
* Copyright 2012
*/
也可以修飾註釋,使它們比較突出:
/******************************************* *
* This isavery important comment *
* so please read this. *
******************************************* * /
使用另一種記號,可以在程式碼行的末尾新增一個註釋,如下所示:
printf("Beware the Ides of March!"); // This line displays a quotation
程式碼行上兩個斜槓後面的所有內容都會被編譯器忽略。這種形式的註釋沒有前一種
記號那麼凌亂,尤其是在註釋只佔一行的情形下。
應養成給程式新增註釋的習慣,當然程式也可以沒有註釋,但在編寫較長的程式時,
可能會忘記這個程式的作用或工作方式。新增足夠的註釋,可確保日後自己(和其他程式
員)能理解程式的作用和工作方式。
下面給程式再新增一些註釋:
/* Program 1.3 Another Simple C Program - Displaying a Quotation */
#include <stdio.h> // This is a preprocessor directive
int main(void) // This identifies the function main()
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
可以看出,使用註釋是一種非常有效的方式,可以解釋程式中要發生的事情。註釋
可以放在程式中的任意位置,說明程式碼的一般作用,指定程式碼是如何工作的。
1.8.2 預處理指令
下面的程式碼行:

#include <stdio.h> // This is a preprocessor directive
嚴格說來,它不是可執行程式的一部分,但它很重要,事實上程式沒有它是不執行
的。符號#表示這是一個預處理指令(preprocessing directive),告訴編譯器在編譯原始碼之
前,要先執行一些操作。編譯器在編譯過程開始之前的預處理階段處理這些指令。預處
理指令相當多,大多放於程式原始檔的開頭。
在這個例子中,編譯器要將 stdio.h 檔案的內容包含進來,這個檔案稱為標頭檔案(header
file),因為它通常放在程式的開頭處。在本例中,標頭檔案定義了 C 標準庫中一些函式的
資訊,但一般情況下,標頭檔案指定的資訊應由編譯器用於在程式中整合預定義函式或其
他全域性物件,所以有時需要建立自己的標頭檔案,以用於程式。本例要用到標準庫中的
printf()函式,所以必須包含 stdio.h 標頭檔案。 stdio.h 標頭檔案包含了編譯器理解 printf()以及
其他輸入/輸出函式所需要的資訊。名稱 stdio 是標準輸入/輸出(standard input/output)的縮
寫。 C 語言中所有標頭檔案的副檔名都是.h,本書的後面會用到其他標頭檔案。
注意:
在一些系統中,標頭檔案名是不區分大小寫的,但在#include 指令裡,這些檔名通
常是小寫。
每個符合 C11 標準的 C 編譯器都有一些標準的標頭檔案。這些標頭檔案主要包含了與 C
標準庫函式相關的宣告。所有符合該標準的 C 編譯器都支援同一組標準庫函式,有同一


組標準標頭檔案,但一些編譯器有額外的庫函式,它們提供的功能一般是執行編譯器的計
算機所專用的。
注意:
附錄 E 列出了所有的標準標頭檔案。
1.8.3 定義 main()函式
下面的 5 行指令定義了 main()函式:
int main(void) // This identifies the function main()
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
函式是兩個括號之間執行某組操作的一段程式碼。每個 C 程式都由一個或多個函式組
成,每個 C 程式都必須有一個 main()函式——因為每個程式總是從這個函式開始執行。
因此假定建立、編譯、連結了一個名為 progname.exe 的檔案。執行它時,作業系統會執
行這個程式的 main()函式。
定義 main()函式的第一行程式碼如下:
int main(void) // This identifies the function main()
它定義了 main()函式的起始,注意這行程式碼的末尾沒有分號。定義 main()函式的第
一行程式碼開頭是一個關鍵字 int,它表示 main()函式的返回值的型別,關鍵字 int 表示 main()
函式返回一個整數值。執行完 main()函式後返回的整數值表示返回給作業系統的一個代
碼,它表示程式的狀態。在下面的語句中,指定了執行完 main()函式後要返回的值:
return 0; // This returns control to the operating system
這個 return 語句結束 main()函式的執行,把值 0 返回給作業系統。從 main()函式返
回 0 表示,程式正常終止,而返回非 0 值表示異常。換言之,在程式結束時,發生了不
應發生的事情。
緊跟在函式名 main 後的括號,帶有函式 main()開始執行時傳遞給它的資訊,在這個
例子裡,括號內是 void,表示沒有給函式 main()傳遞任何資料,後面會介紹如何將資料
傳遞給函式 main()或程式內的其他函式。
函式 main()可以呼叫其他函式,這些函式又可以呼叫其他函式。對於每個被呼叫的
函式,都可以在函式名後面的括號中給函式傳遞一些資訊。在執行到函式體中的 return
語句時,就停止執行該函式,將控制權返回給呼叫函式(對於函式 main(),則將控制權返
回給作業系統)。一般函式會定義為有返回值或沒有返回值。函式返回一個值時,該值總
是特定的型別。對於函式 main(),返回值的型別是 int,即整數。

1.8.4 關鍵字
在 C 語言中, 關鍵字是有特殊意義的字, 所以在程式中不能將關鍵字用於其他目的。
關鍵字也稱為保留字。在前面的例子裡, int 就是一個關鍵字, void 和 return 也是關鍵字。
C 語言有許多關鍵字,我們在學習 C 語言的過程中,將逐漸熟悉這些關鍵字。附錄 C 列
出了完整的 C 語言關鍵字表。
1.8.5 函式體
main()函式的一般結構如圖 l-2 所示:
main()函式的結構
函式頭
指定函式定義的開頭和函式名稱
起始括號
定義函式體的開頭
函式體
包含所有的語句,它們定義了函式
執行時要完成的任務
結束括號
定義函式體的結束

圖 l-2 函式 main()的結構
函式體是在函式名稱後面位於起始及結束兩個大括號之間的程式碼塊。它包含了定義
函式功能的所有語句。這個例子的 main()函式體非常簡單,只有兩個語句:
{ // This marks the beginning of main()
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0; // This returns control to the operating system
} // This marks the end of main()
每個函式都必須有函式體,但函式體可以是空的,僅有起始及結束兩個大括號,裡
面沒有任何語句,在這種情況下,這個函式什麼也不做。
這樣的函式有什麼用?事實上,在開發一個包含很多函式的程式時,這種函式是非
常有用的。我們可以宣告一些用來解決手頭問題的空函式,確定需要完成的程式設計工作,
再為每個函式建立程式程式碼。這個方法有助於條理分明地、系統地建立程式。

注意:
程式 1.3 將大括號單獨排為一行,並縮排大括號之間的程式碼。這麼做可清楚地表示
括號框起來的語句塊從哪裡起始和結束。大括號之間的語句通常縮排兩個或多個空格,
使大括號突出在前。這是個很好的程式設計格式,可以使語句塊更容易閱讀。
程式碼中的大括號可以用其他方式擺放。例如:
int main(void) {
printf("Beware the Ides of March!"); // This line outputs a quotation
return 0;
}
提示:
無論原始碼採用什麼方式擺放,都要一直採用這種方式,這很重要。
1.8.6 輸出資訊
例子中的 main()函式體包含了—個呼叫 printf()函式的語句:
printf("Beware the Ides of March!"); // This line outputs a quotation
printf()是—個標準的庫函式,它將函式名後面引號內的資訊輸出到命令列上(實際上
是標準輸出流,預設為命令列)。在這個例子中,呼叫這個函式會顯示雙引號內的一段警
示語:雙引號內的字串稱為字串字面量。注意這行程式碼用分號作為結尾。
1.8.7 引數
包含在函式名(如上面語句中的 printf()函式)後的圓括號內的項稱為引數,它指定要
傳送給函式的資料。當傳送給函式的引數多於一個時,要用逗號分開。
在上面的例子中,函式的引數是雙引號內的文字字串。如果不喜歡例子中引號內
的文字,可以改用自己想輸出的句子。例如,使用如下語句:
printf("Out, damned Spot! Out I say!");
修改原始碼後,必須再次編譯及連結程式,才可執行。
注意:
與 C 語言中所有可執行的語句一樣, printf()行的末尾必須有分號(這與定義語句或指
令語句不同)。這是一個很容易犯的錯誤,尤其是初次使用 C 程式設計的人,老是忘了分號。
1.8.8 控制符
前面的程式可以改為輸出兩段句子。輸入以下的程式碼:
// Program 1.4 Another Simple C Program - Displaying a Quotation
#include <stdio.h>

int main(void)
{
printf("My formula for success?\nRise early, work late, strike oil.\n");
return 0;
}
輸出的結果是:
My formula for success?
Rise early, work late, strike oil.
在 printf()語句中,在文字的開頭和第一句的後面,增加了字元\n,它是另一個轉義序
列,代表換行符。這樣輸出游標就會移動到下一行,後續的輸出就會顯示在新行上。
反斜槓(\)在文字字串裡有特殊的意義,它表示轉義序列的開始。反斜槓後面的字元
表示是哪種轉義序列。對於\n, n 表示換行。還有其他許多轉義序列。顯然,反斜槓是有
特殊意義的,所以需要一種方式在字串中指定反斜槓。為此,應使用兩個反斜槓(\\)。
輸入以下的程式:
// Program 1.5 Another Simple C Program - Displaying Great Quotations
#include <stdio.h>
int main(void)
{
printf("\"It is a wise father that knows his own child.\"\nShakespeare\n");
return 0;
}
輸出的結果如下:
"It is a wise father that knows his own child."
Shakespeare
輸出中包含雙引號,因為在字串中使用了雙引號的轉義序列。 Shakespeare 顯示在
下一行,因為在\”的後面有\n 轉義序列。
在輸出字串中使用轉義序列\a 可以發出聲音,說明發生了有趣或重要的事情。輸
入以下的程式並執行:
// Program 1.6 A Simple C Program – Important
#include <stdio.h>
int main(void)
{
printf("Be careful!!\n\a");
return 0;
}
這個程式的輸出如下所示且帶有聲音。仔細聆聽,電腦的揚聲器會發出鳴響。
Be careful!!
轉義序列\a 表示發出鳴響。表 1-1 是轉義序列表。
表 1-1 轉義序列

轉 義 序 列 說 明
\n 換行
\r Enter鍵
\b 退後一格
\f 換頁
\t 水平製表符
\v 垂直製表符
\a 發出鳴響
\? 插入問號(?)
\" 插入雙引號(")
\' 插入單引號(')
\\ 插入反斜槓(\)


試著在螢幕上顯示多行文字,在該文字中插入空格。使用 \n 可以把文字放在多個行
上,使用\t 可以給文字加上空格。本書將大量使用這些轉義序列。
1.8.9 三字母序列
一般可以直接在字串中使用問號。 \?轉義序列存在的唯一原因是,有 9 個特殊的
字母序列,稱為三字母序列,這是包含三個字母的序列,分別表示#、 [、 ]、 \、 ^、 ~、 \、
{和}:
??=轉換為# ??(轉換為[ ??)轉換為]
??/轉換為\ ??<轉換為{ ??>轉換為}
??'轉換為^ ??!轉換為| ??-轉換為~
在 International Organization for Standardization(ISO)不變的程式碼集中編寫 C 程式碼時,
就需要它們,因為它沒有這些字元。這可能不適用於你。可以完全不理會它們,除非希
望編寫如下語句:
printf("What??!\n");
這個語句生成的輸出如下:
What|
三字母序列??!會轉換為|。為了獲得希望的輸出,需要把上述語句寫成:
printf("What?\?!\n");
現在三字母序列不會出現,因為第二個問號用其轉義序列指定。使用三字母序列時,
編譯器會發出一個警告,因為通常是不應使用三字母序列的。

1.9 前處理器
上述示例介紹瞭如何使用預處理指令,把標頭檔案的內容包含到原始檔中。編譯的預
處理階段可以做的工作遠不止此。除了指令之外,原始檔還可以包含宏。宏是提供給預
處理器的指令,來新增或修改程式中的 C 語句。宏可以很簡單,只定義一個符號,例如
INCHES_PER_FOOT,只要出現這個符號,就用 12 替代。其指令如下:
#define INCHES_PER_FOOT 12
在原始檔中包含這個指令,則程式碼中只要出現 INCHES_PER_FOOT,就用 12 替代
它。例如:
printf("There are %d inches in a foot.\n", INCHES_PER_FOOT);
預處理後,這個語句變成:
printf("There are %d inches in a foot.\n", 12);
INCHES_PER_FOOT 不再出現,因為該符號被#define 指令中指定的字串替代。對
於原始檔中的每個符號例項,都會執行這個替代。
宏也可以很複雜,根據特定的條件把大量程式碼新增到原始檔中。這裡不進一步介紹。
第 13 章將詳細討論前處理器宏。在此之前我們會遇到一些宏,那時會解釋它們。
1.10 C 語言開發程式
如果讀者從未寫過程式,對 C 語言開發程式的過程就不會很清楚,但它和我們日常
生活的許多事務是相同的,萬事開頭難。一般首先大致確定要實現的目標,接著把該目
標轉變成比較準確的規範。有了這個規範後,就可以制訂達到最終目標的一系列步驟了。
就好比光知道要蓋房子是不夠的,還得知道需要蓋什麼樣的房子,它有多大,用什麼材
料,要蓋在哪裡。這種詳細規劃也需要運用到編寫程式上。下面介紹編寫程式時需要完
成的基本步驟。房子的比喻是很有幫助的,因此就利用這個比喻。
1.10.1 瞭解問題
第一步是弄清楚要做什麼。在不清楚應提供什麼設施:多少間臥房、多少間浴室、
各房間多大等等之前就開始建造房子,會有不知所措之感。所有這些都會影響建造房子
所需的材料和工作量,從而影響整個房子的成本。一般來說,在滿足需求和完成專案的
有限資金、人力及時間之間總會達成某種一致。
這和開發一個任意規模的程式是相同的。即使是很簡單的問題,也必須知道有什麼
輸入,對輸入該做什麼處理,要輸出什麼,以及輸出哪種格式。輸入可以來自鍵盤,也
可以來自磁碟檔案的資料,或來自電話或網路的資訊。輸出可以顯示在螢幕上,或列印

出來,也可以是更新磁碟上的資料檔案。
對於較複雜的程式,需要多瞭解程式的各個方面。清楚地定義程式要解決的問題,
對於理解制訂最終方案所需的資源與努力,是絕對必要的一部分。好好考慮這些細節,
還可以確定專案是否切實可行。對於新專案缺乏精準、詳細的規範,常常使專案所花的
時間和資金大大超出預算,因而中斷專案的例子有很多。
1.10.2 詳細設計
要建造房子,必須有詳細的計劃。這些計劃能讓建築工人按圖施工,並詳細描述房
子如何建造——具體的尺寸、要使用的材料等。還需要確定何時完成什麼工作。例如,
在砌牆之前要先挖地基,所以這個計劃必須把工作分為可管理的單元,以便執行起來井
然有序。
寫程式也是一樣。首先將程式分解成許多定義清楚且互相獨立的小單元,描述這些
獨立單元相互溝通的方式,以及每個單元在執行時需要什麼資訊,從而開發出富有邏輯、
相互獨立的單元。把大型程式編寫為一個大單元肯定是不可行的。
1.10.3 實施
有了房子的詳細設計,就可以開始工作了。每組建築工人必須按照進度完成他們的
工作。在下一階段開始前,必須先檢查每個階段是否正確完成。省略了這些檢查,將可
能導致整棟房子倒塌。
當然,假使程式很大,可以一次編寫—部分。一個部分完成後,再寫下—部分。每
個部分都要基於詳細的設計規範,在進行下一個部分之前,應儘可能詳細地檢查每個部
分的功能。這樣,程式就會逐步完成預期的任務。
大型程式設計專案常常涉及一組程式設計師。專案應分成相當獨立的單元,分配給程式設計師組
中的各個成員。這樣就可以同時開發幾個程式碼單元。如果程式碼單元要相互連線為一個整
體,就必須精確定義程式碼單元與程式其餘部分之間的互動。
1.10.4 測試
房子完成了,還要進行許多測試:排水裝置、水電設施、暖氣等。任何部分都有可
能出問題,這些問題必須解決。這有時是一個反覆的過程,一個地方的問題可能會造成
其他地方出問題。
這個機制與寫程式是類似的。每個程式模組——組成程式的單元——都需要單獨測
試。若它們工作不正常,就必須除錯。除錯(Debugging)是一個找出程式中的問題及更正
錯誤的過程。除錯的由來有個說法,曾經有人在查詢程式的錯誤時,使用計算機的電路
圖來跟蹤資訊的來源及其處理方式,竟然發現計算機程式出現錯誤,是因為一隻蟲子在
電腦裡,讓裡面的線路短路而發生的,後來, bug 這個詞就成了程式錯誤的代名詞。
對於簡單的程式,通常只要檢查程式碼,就可以找出錯誤。然而一般來說,除錯過程

通常會使用偵錯程式臨時插入一些程式碼,確定在出錯時會發生什麼。這包括插入斷點,當
暫停執行,檢查程式碼中的值。還可以單步執行程式碼。如果沒有偵錯程式,就要加入額外的
程式程式碼,輸出一些資訊,來確定程式中事件的發生順序,以及程式執行時生成的中間
值。在大型的程式裡,還需要聯合測試各個程式模組,因為各個模組或許能正常工作,
但並不保證它能和其他模組一起正常工作。在程式開發的這個階段,有個專業術語叫集
成測試(integration testing)。
1.11 函式及模組化程式設計
到目前為止,“函式”這個詞已出現過好幾次了,如 main()、 printf()、函式體等。下
面將深入研究函式是什麼,為什麼它們那麼重要。
大多數程式語言(包含 C 語言)都提供了一種方法,將程式切割成多個段,各段都可
以獨立編寫。在 C 語言中,這些段稱為函式。一個函式的程式程式碼與其他函式是相互隔
絕的。函式與外界有一個特殊的介面,可將資訊傳進來,也可將函式產生的結果傳出去。
這個介面在函式的第一行即在函式名的地方指定。
圖 1-3 的簡單程式例子由 4 個函式組成,用於分析棒球分數。
程式控制
棒球隊及隊員輸入
棒球隊及隊員
的排名輸出
棒球隊及隊員分析
棒球分數的模組分析

圖 1-3 模組化程式設計
這 4 個函式都完成一個指定的、定義明確的工作。程式中操作的執行由一個模組
main()總體掌控。一個函式負責讀入及檢查輸入資料,另一個函式進行分析。讀入及分
析了資料後,第 4 個函式就輸出球隊及球員的排名。
將程式分割成多個易於管理的小單元,對程式設計是非常重要的,其理由如下:
● 可以單獨編寫和測試每個函式,大大簡化了使整個程式運轉起來的過程。
● 幾個獨立的小函式比一個大函式更容易處理和理解。
● 庫就是供人使用的函式集。因為它們是事先寫好,且經過測試,能正常工作,所
以可以放心地使用,無須細究它的程式碼細節。這就加快了開發程式的速度,因為

我們只需要關注自己的程式碼,這是 C 語言的一個基本組成部分。 C 語言中豐富
的函式庫大大增強了 C 語言的能力。
● 也可以編寫自己的函式庫, 應用於自己感興趣的程式型別。 如果發現經常編寫某
個函式, 就可以編寫它的通用版本, 以滿足自己的需求, 並將它加入自己的庫中。
以後需要用到這個函式時,就可使用它的庫版本了。
● 在開發包含幾千到幾百萬行程式碼的大型程式時,可以由一些程式設計團隊來進
行,每個團隊負責一個指定的函式子組,最後把它們組成完整的程式。
第 8 章將詳細介紹 C 函式。 C 程式的結構在本質上就是函式的結構,本章的第一個
例子就用到一個標準的庫函式 printf()。
注意:
在其他一些程式語言中,用術語“方法”表示自包含的程式碼單元。因此方法的含義
與函式相同。
試試看:將所學的知識用於實踐
下面的例子將前面學到的知識用於實踐。首先,看看下面的程式碼,檢查自己是否理
解它的作用。然後輸入這些程式碼,編譯、連結並執行,看看會發生什麼。
// Program 1.7 A longer program
#include <stdio.h> // Include the header file for input and output
int main(void)
{
printf("Hi there!\n\n\nThis program is a bit");
printf(" longer than the others.");
printf("\nBut really it's only more text.\n\n\n\a\a");
printf("Hey, wait a minute!! What was that???\n\n");
printf("\t1.\tA bird?\n");
printf("\t2.\tA plane?\n");
printf("\t3.\tA control character?\n");
printf("\n\t\t\b\bAnd how will this look when it prints out?\n\n");
return 0;
}
輸出如下:
Hi there!
This program is a bit longer than the others.
But really it's only more text.
Hey, wait a minute!! What was that???
1. A bird?
2. A plane?
3. A control character?
And how will this look when it prints out?

程式碼的說明
這個程式看起來有點複雜,這只是因為括號內的文字字串包含了許多轉義序列。
每個文字字串都由一對雙引號括起來。但這個程式只是連續呼叫 printf()函式,說明屏
幕輸出是由傳送給 printf()函式的資料所控制。
本例透過預處理指令包含了標準庫中的 stdio.h 檔案:

#include <stdio.h> // Include the header file for input and output
這是一個預處理指令,因為它以符號#開頭。 stdio.h 檔案提供了使用 printf()函式所
需的定義。
然後,定義 main()函式頭,指定它返回一個整數值:
int main(void)
括號中的 void 關鍵字表示不給 main()函式傳遞資訊。下一行的大括號表示其下是函
數體:
{
下一行語句呼叫標準庫函式 printf(),將“ Hi there!”輸出到螢幕上,接著空兩行,
輸出“ This program is a bit”。
printf("Hi there!\n\n\nThis program is a bit");
空兩行是由 3 個轉義序列\n 生成的。轉義序列\n 會把字元顯示在新行上。第一個轉
義序列\n 結束了包含“ Hi there!” 的行, 之後的兩個轉義序列\n 生成兩個空行, 文字“ This
program is a bit”顯示在第 4 行上。這行程式碼在螢幕上生成了 4 行輸出。
下一個 printf()生成的輸出跟在上一個 printf()輸出的最後一個字元後面。下面的語句
輸出文字“ longer than the others.”,其中的第一個字元是一個空白:
printf(" longer than the others.");
這個輸出跟在上一個輸出的後面,緊臨 bit 中的 t。所以在文字的開頭需要一個空格,否
則計算機就會顯示“ This program is a bitlonger than the others.”,這不是我們希望的結果。
下一個語句在輸出前會先換行,因為雙引號中文字字串的開頭是\n:
printf("\nBut really itS only more text.\n\n\n\a\a");
顯示完文字後會空兩行(因為有 3 個\n 轉義序列),然後發出兩次鳴響。下一個螢幕
輸出從空的第二行開始。
下一個輸出語句如下:
printf("Hey, wait a minute!! What was that???\n\n");
輸出文字後空一行。其後的輸出在空的這行開始。


以下
3 行語句各插入一個製表符,顯示一個數字後,再插入另一個製表符,之後是
一些文字,結束後換行。這樣,輸出更容易閱讀:
printf("\t1.\tA bird?\n");
printf("\t2.\tA plane?\n");
printf("\t3.\tA control character?\n");
這幾個語句會生成 3 行帶編號的輸出。
下一語句先輸出一個換行符,所以在前面輸出的後面是一個空行,然後輸出兩個制
表符和兩個空格,接著退回兩個空格,最後顯示文字並換行:
printf("\n\t\t\b\bAnd how will this look when it prints out?\n\n");
函式體中的最後一個語句如下:
return 0;
這個語句結束 main() 的執行,把 0 返回給作業系統。
結束大括號表示函式體結束:
}
注意:
輸出中製表符和退格的實際效果隨編譯器的不同而不同。
1.12 常見錯誤
錯誤是生活中的一部分。用 C 語言編寫計算機程式時,必須用編譯器將原始碼轉換
成機器碼,所以必須用非常嚴格的規則控制使用
C 語言的方式。漏掉一個該有的逗點,
或新增不該有的分號,編譯器都不會將程式轉換成機器碼。
即使實踐了多年,程式中也很容易出現輸入錯誤。這些錯誤可能在編譯或連結程式
時找出。但有些錯誤可能使程式執行時,表面上看起來正常,卻不定時地出錯,這就需
要花很多時間來跟蹤錯誤了。
當然,不是隻有輸入錯誤會帶來問題,具體實施時也常常會發現問題。在處理程式
中複雜的判斷結構時,很容易出現邏輯錯誤。從語言的觀點看,程式是正確的,編譯及
執行也正確,但得不到正確的結果。這類錯誤最難查詢。
1.13 要點
溫習第一個程式是個不錯的方法,圖 1-4 列出了重點。
這表示註釋的開頭
表示 main()返回一個整數值
這表示註釋的結尾
//後面的所有內容都是註釋,因此
編譯器會忽略它們
是一個標準標頭檔案,使用輸入輸
出函式如 printf()時需要包含它
這個大括號表示 main()函式體
的結尾
這個大括號表示 main()函式體
的開頭
函式體是包含在最外層括號中
的所有程式碼
這個語句結束 main()的執行, 把
控制權返回給作業系統
//後面的所有內容都是註釋,因
此編譯器會忽略它們
Include 指令把外部檔案的內容包
含到原始檔中

圖 1-4 簡單程式的要素
1.14 小結
本章編寫了幾個 C 程式。我們學習了許多基礎知識,本章的重點是介紹一些基本概
念,而不是詳細探討 C 程式語言。現在讀者應該對編寫、編譯及連結程式很有信心了。
也許讀者目前對如何構建 C 程式只有模糊的概念。以後學了更多的 C 語言知識,編寫了
一些程式後,就會清楚明白了。
下一章將學習較複雜的內容,而不只是用 printf()輸出文字。我們要處理資訊,得到
更有趣的結果。另外, printf()不只是顯示文字字串,它還有其他用途。
1.15 習題
以下的習題能讓讀者測試本章所學的成果。如果有不懂的地方,可以翻看本章的內
容,還可以從 Apress 網站 的 Source Code/Download 部分下載答案,
但這應是最後一種方法。
習題 1.1 編寫一個程式,用兩個 printf()語句分別輸出自己的名字及地址。
習題 1.2 將上一個練習改成所有的輸出只用一個 printf()語句。
習題 1.3 編寫一個程式,輸出下列文字,格式如下所示:
"It's freezing in here," he said coldly.

購買地址:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26421423/viewspace-2217397/,如需轉載,請註明出處,否則將追究法律責任。

相關文章