《Head First C 中文版》審讀筆記(一)
不顧一切找 Frank
《Head First C 中文版》第 94 頁:
1: #include <stdio.h>
2: #include <string.h>
3:
4: char tracks[][80] = {
5: "I left my heart in Harvard Med School",
6: "Newark, Newark - a wonderful town",
7: "Dacing with a Dork",
8: "From here to maternity",
9: "The girl from Iwo Jima",
10: };
11:
12: void find_track(char search_for[])
13: {
14: int i;
15: for (i = 0; i < 5; i++) {
16: if (strstr(tracks[i], search_for))
17: printf("Track %i: '%s'\n", i, tracks[i]);
18: }
19: }
20:
21: int main()
22: {
23: char search_for[80];
24: printf("Search for: ");
25: fgets(search_for, 80, stdin);
26: find_track(search_for);
27: return 0;
28: }
試駕
第 95 頁:
下面開啟終端,看看程式碼能否工作:
> gcc text_search.c -o text_search && ./text_search Search for: town Track 1: 'Newark, Newark - a wonderful town' >
好訊息,程式工作了!
到目前為止,這個程式是你寫過最長的一個,但它做了更多的事情。程式建立了一個字元陣列,並利用標準庫中的字串處理函式搜尋陣列中的歌名,最後找到了使用者想要找的歌曲。
程式其實不工作
實際上,在我的機器上,這個程式的執行結果是:
> gcc text_search.c -o text_search && ./text_search
Search for: town
>
並且,不論你輸入什麼,這個程式都沒有找到任何歌曲。為什麼會這樣呢?
C 語言庫函式 fgets()
問題出在這個程式第 25 行的fgets()
上面:
char *fgets(char *s, int size, FILE *stream);
實際上,fgets() 函式從輸入流stream
讀取最多size - 1
個字元到緩衝區s
中。如果遇到EOF
或者換行符則停止。如果讀到換行符,則換行符也儲存到s
中。然後在緩衝區s
中附加一個null
位元組('\0'
)。
這就是程式不工作的原因了,我們的search_for
中包含了換行符,所以find_track()
函式無法搜尋到任何歌曲。
修正方案一
簡單地使用以下語句代替第 25 行的語句:
gets(search_for);
這個程式就可以正常工作了。因為gets()
函式從標準輸入讀取字元到緩衝區中,也是遇到EOF
或者換行符就停止。如果讀取換行符,就扔掉它,並不儲存到search_for
中。這正符合我們的要求。
且慢,gets()
函式是個十分危險的傢伙,非常容易造成緩衝區溢位。實際上,gcc 編譯器在編譯時會給出以下警告:
text_search.c: 在函式‘main’中:
text_search.c:25:3: 警告:不建議使用‘gets’(宣告於 /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(search_for);
^
C 語言庫函式 gets()
《Head First 中文版》第 67 頁:
fgets() 函式其實是從一個更古老的函式演變而來的,它叫 gets() 。
儘管我們說 fgets() 比 scanf() 更安全,但它的祖先 gets() 才是最危險的傢伙。為什麼?因為 gets() 函式沒有任何限制:
char dangerous[10]; gets(dangerous); // 別!我是認真的,千萬別用它。
雖然 gets() 函式已經行走江湖很多年了,但真的不應該用它。
修正方案二
將第 25 行替換為以下語句:
scanf("%79s", search_for);
這也可以正確工作。注意,一定要用%79s
格式串,不要使用%s
格式串,否則也非常容易造成緩衝區溢位。
修正方案三
但是,修正方案二也有個小缺點,如果我們輸入的字串中包含空格,scanf()
函式只會讀取空格之前的字元。所以就有以下修正方案三:
在第 25 行之後增加一句:
remove_tail_newline(search_for);
在第 21 行之前增加一個函式
remove_tail_newline
,用於刪除字串尾部的換行符:void remove_tail_newline(char str[]) { size_t len = strlen(str); if (len > 0 && str[len - 1] == '\n') str[len - 1] = '\0'; }
這樣,就完美解決這個問題。注意,remove_tail_newline
函式不能寫成以下樣子:
str[strlen(str) - 1] = '\0';
這是因為:
- 字串
str
的尾部可能不包含換行符,比如標準輸入被重定向到一個不包括換行符的檔案中。 - 字串
str
的長度還有可能為零。
修正方案四
將第 25 行替換為以下語句:
scanf("%79[^\n]\n", search_for);
這和修正方案二很像,只不過是把格式符 %79s 替換為 %79[^\n]\n 。這能夠讀入空格,避免修正方案二的缺點。但 scanf() 函式的這個格式符在 K&R 的經典著作中沒有提到,應該是後來增加的。我想現代的 C 編譯器應該都會支援。(老古董的 C 編譯器就難說了)
附註:根據 @It 的評論,這個修正方案有個問題:輸入完回車後不會馬上響應,需要再輸入點什麼後才返回結果。所以這個方案不太靠譜,使用者體驗不好。看來這種格式符更適用於從檔案中讀取輸入等不與使用者發生互動的場合。
再次附註:把格式符從%79[^\n]\n
改為%79[^\n]
可以解決上述問題。
其他影響
《Head First C 中文版》第 91 頁、第 93 頁也涉及到上述text_search.c
中main()
函式,也要進行相應修改。
參考資料
相關文章
- 《Head First Java》20201017讀書筆記Java筆記
- 《Head First Java》20200927讀書筆記Java筆記
- 《Head First Java》20201009讀書筆記Java筆記
- Head First設計模式讀書筆記設計模式筆記
- Head first PHP & MySQL 中文版pdfPHPMySql
- Head First Python (一)Python
- Head First HTML 與 CSS(第二版)學習筆記HTMLCSS筆記
- Head First Java學習筆記(7):繼承與多型Java筆記繼承多型
- Head First 設計模式筆記 3.裝飾者模式設計模式筆記
- Head First C 電子書pdf下載
- Head First HTML and CSS (八)HTMLCSS
- 《Head First Android》讀後感,電子書PDF下載Android
- elasticsearch-head 筆記Elasticsearch筆記
- 《Head First 設計模式》:策略模式設計模式
- head first java第一章的學習Java
- [head first 設計模式] 第一章 策略模式設計模式
- 「HEAD-FIRST」之觀察者模式模式
- 《Head First 設計模式》:模板方法模式設計模式
- 《Head First 設計模式》:單件模式設計模式
- 《Head First 設計模式》:外觀模式設計模式
- 《Head First 設計模式》:狀態模式設計模式
- 《Head First 設計模式》:剩下的模式設計模式
- 《Head First 設計模式》:組合模式設計模式
- Head First 設計模式(1)-----策略模式設計模式
- 《Head First 設計模式》:工廠方法模式設計模式
- 《Head First 設計模式》:觀察者模式設計模式
- 《Effective C++》讀書筆記C++筆記
- C++讀書筆記:字串C++筆記字串
- 《Effective Objective-C 2.0》讀書/實戰筆記 一Object筆記
- Head First 設計模式(3)----裝飾者模式設計模式
- Head First 設計模式 —— 13. 代理 (Proxy) 模式設計模式
- 《C缺陷與陷阱》讀書筆記筆記
- Head First 設計模式(2)---觀察者(Observer)模式設計模式Server
- 《Head First 設計模式》:與設計模式相處設計模式
- Head First 設計模式 —— 14. 複合 (Compound) 模式設計模式
- PMBook讀書筆記(一)筆記
- 【記】《.net之美》之讀書筆記(一) C#語言基礎筆記C#
- 《C++ Primer》讀書筆記(第一章 開始)C++筆記
- C++筆記 14:審慎使用異常規格(exception specifications)C++筆記Exception