周立功教授數(shù)年之心血之作《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》,電子版已無(wú)償性分享到電子工程師與高校群體,在公眾號(hào)回復(fù)【程序設(shè)計(jì)】即可在線閱讀。書(shū)本內(nèi)容公開(kāi)后,在電子行業(yè)掀起一片學(xué)習(xí)熱潮。經(jīng)周立功教授授權(quán),本公眾號(hào)特對(duì)本書(shū)內(nèi)容進(jìn)行連載,愿共勉之。
第一章為程序設(shè)計(jì)基礎(chǔ),本文為1.8.2 字符串常量第二點(diǎn):字符串的輸入輸出。
(1)scanf()函數(shù)和gets()函數(shù)
在讀取字符串時(shí),scanf()和轉(zhuǎn)換格式符%s只能讀取一個(gè)單詞,比如:
scanf("%s\n", str);
在scanf函數(shù)調(diào)用中,不需要在str前添加&,因?yàn)閟tr是數(shù)組名,編譯器在將它傳遞給函數(shù)時(shí),會(huì)將它當(dāng)作指針來(lái)處理。調(diào)用時(shí),scanf函數(shù)會(huì)跳過(guò)空字符,然后讀入字符并存儲(chǔ)到str中,直到遇到空字符為止,scanf函數(shù)始終會(huì)在字符串末尾存儲(chǔ)一個(gè)空字符。
在程序中經(jīng)常要讀取一整行輸入,而不僅僅是一個(gè)單詞,gets()就是用于處理這種情況的。它讀取整行輸入直至遇到換行符,然后丟棄換行符存儲(chǔ)其余字符,并在這些字符的末尾添加一個(gè)空字符使其成為一個(gè)字符串。它經(jīng)常和puts()配對(duì)使用,該函數(shù)用于顯示字符串,并在末尾添加換行符。即gets()是從標(biāo)準(zhǔn)輸入設(shè)備中輸入若干個(gè)字符,并保存到參數(shù)s指向的字符數(shù)組中,直到文件結(jié)束或讀到一個(gè)換行符。換行符將被丟棄,在輸入最后一個(gè)字符后會(huì)立即寫(xiě)入一個(gè)結(jié)束符'\0'。其函數(shù)原型如下:
char *gets(char *s);
其中的s指向保存輸入字符串的內(nèi)存空間,如果gets()成功地獲得了字符串,則返回s,否則返回NULL。比如,通過(guò)命令行輸入一個(gè)字符'9',但'9'不是整數(shù)9,如果將'9'-'0',則會(huì)得到整數(shù)9。即:
char cStr[256];
int cmdNum;
cmdNum = getchar() - '0';
gets(cStr); //清空緩沖區(qū)
如果將數(shù)組作為參數(shù)傳遞,則傳遞的是指向數(shù)組首元素的指針,當(dāng)gets()作為被調(diào)用函數(shù)時(shí),則完全不知道數(shù)組究竟有多大,而調(diào)用者又不能向gets()傳遞緩沖區(qū)的大小,因此gets()無(wú)法檢查數(shù)組的長(zhǎng)度。顯然必須有足夠的空間保存輸入的字符串,否則可能出現(xiàn)莫名其妙的問(wèn)題。如果你故意將尺寸很大的數(shù)據(jù)傳遞給gets(),就可以達(dá)到數(shù)組越界且改寫(xiě)返回地址的目的。1988年名震互聯(lián)網(wǎng)的“互聯(lián)網(wǎng)蠕蟲(chóng)”病毒,就是利用了gets()的這個(gè)弱點(diǎn)。
由于gets()的不安全行為造成了隱患,因此制定C11標(biāo)準(zhǔn)的委員采取了強(qiáng)硬的態(tài)度,直接從標(biāo)準(zhǔn)中廢除了gets()函數(shù)。不妨自己編寫(xiě)一個(gè)輸入函數(shù),假設(shè)函數(shù)不會(huì)跳過(guò)空字符,在第一個(gè)換行符(不存儲(chǔ)到字符串中)處停止讀取,且忽略額外的字符。其函數(shù)原型如下:
int readLine(char str[], int n);
readLine()函數(shù)主要由一個(gè)循環(huán)構(gòu)成,只要str中還有空間,此循環(huán)就會(huì)調(diào)用getchar()函數(shù)逐個(gè)讀入字符并將它存儲(chǔ)在str中,在讀入換行符時(shí)循環(huán)終止,詳見(jiàn)程序清單 1.40。
程序清單 1.40 readLine()函數(shù)的實(shí)現(xiàn)
1 int readLine(char str[], int n)
2 {
3 int ch, i = 0;
4
5 while((ch = getchar()) != '\n')
6 if(i < n)
7 str[i++] = ch;
8 str[i] = '\0';
9 return 0;
10 }
(2)printf()函數(shù)和puts()函數(shù)
轉(zhuǎn)換格式符s%允許printf()寫(xiě)字符串,與puts不同的是,printf()不會(huì)自動(dòng)地在每個(gè)字符串的末尾加上一個(gè)換行符,因此必須在參數(shù)中指明應(yīng)該在哪里使用換行符。比如:
char str[] = "hello world";
printf("%s\n", str);
printf()會(huì)逐個(gè)寫(xiě)字符串中的字符,直到遇到空字符為止。如果只想顯示字符串的一部分,可以使用轉(zhuǎn)換格式符%.ps,這里的p是顯示的字符數(shù)量。比如,顯示hello:
printf("%.5s\n", str);
雖然printf()用起來(lái)比較復(fù)雜,但可以打印多個(gè)字符串。除了printf(),C標(biāo)準(zhǔn)庫(kù)還提供了puts(),其函數(shù)原型如下:
int puts(const char *s);
其中,s為指定輸出的字符串,puts()函數(shù)將參數(shù)s指向的字符串輸出到標(biāo)準(zhǔn)輸出設(shè)備中,但不輸出結(jié)束符'\0'。在輸出字符串后,puts()函數(shù)會(huì)多輸出一個(gè)換行符'\n',然后通過(guò)標(biāo)準(zhǔn)輸出設(shè)備顯示指定的字符串。如果顯示成功,則返回0,否則返回預(yù)定義常量EOF。puts()如何知道在何處停止呢?該函數(shù)在遇到空字符時(shí)就停止輸出,所以必須確保有空字符。
(3)fgets()函數(shù)和fputs()函數(shù)
fgets()和fputs()分別是gets()和puts()針對(duì)文件的定制的版本,fgets()通過(guò)第2個(gè)參數(shù)限制讀入的字符數(shù)來(lái)解決溢出的問(wèn)題,該函數(shù)專門(mén)用于處理文件輸入。如果第2個(gè)參數(shù)的值是n,那么fgets()將讀入n-1個(gè)字符,或遇到第1個(gè)換行符為止。如果讀到一個(gè)換行符將它存儲(chǔ)在字符串中,這點(diǎn)與gets()不同,gets()會(huì)丟棄換行符。
fgets()的第1個(gè)參數(shù)與gets()一樣,也是存儲(chǔ)輸入位置的地址(char *類型),第2個(gè)參數(shù)是一個(gè)整數(shù),表示待輸入字符串的大小,最后一個(gè)參數(shù)是文件指針,指定待讀取文件。如果讀入從鍵盤(pán)輸入的數(shù)據(jù),則以標(biāo)準(zhǔn)輸入stdin作為參數(shù),該標(biāo)識(shí)定義在stdio.h中。其調(diào)用示例如下:
fgets(buf, STLEN, fp);
其中,buf是char類型數(shù)組的名稱,STLEN是字符串的大小,fp是指向FILE的指針。以上面的gets()為例,fgets()讀取輸入直到第1個(gè)換行符的后面,或讀到文件結(jié)尾,或讀取STLEN-1個(gè)字符,然后fgets()在末尾添加一個(gè)空字符使之成為一個(gè)字符串,字符串的大小是其字符數(shù)加上一個(gè)空字符。如果fgets()在讀到字符上限之前已經(jīng)讀完一整行,它會(huì)將表示行結(jié)尾的換行符放在空字符前面。fegts()在遇到EOF時(shí)將返回NULL,因此可以利用這一機(jī)制檢查是否到達(dá)文件結(jié)尾。如果未遇到EOF,則返回它的地址。
fgets()存儲(chǔ)換行符有好處也有壞處,壞處是你可能不想將換行符存儲(chǔ)在字符串中,這樣的換行符會(huì)帶來(lái)一些麻煩。好處是對(duì)于存儲(chǔ)的字符串而言,檢查末尾是否有換行符可以判斷是否讀取了一整行。如果不是一整行,則要妥善處理一行中剩下的字符。
首先,如何處理?yè)Q行符?一個(gè)方法是在已經(jīng)存儲(chǔ)的字符串中查找換行符,并將其替換成空字符。假設(shè)\n在st中:
while(st[i] != '\n' )
i++;
st[i] = '\0';
其次,如果仍有字符串留在輸入行怎么辦?一個(gè)可行的辦法是,如果目標(biāo)數(shù)組裝不下一整行輸入,就丟棄那些多出的字符。即讀取但不存儲(chǔ)輸入,包括\n:
while(getchar() != '\0')
continue;
為何要丟棄輸入行中余下的字符?因?yàn)檩斎胄兄卸喑鰜?lái)的字符會(huì)留在緩沖區(qū)中,成為下一次讀取語(yǔ)句的輸入。比如,如果下一條讀取語(yǔ)句要讀取的是double類型的值,就可能導(dǎo)致程序崩潰,而丟棄輸入行余下的字符是為了保證讀取語(yǔ)句與鍵盤(pán)輸入同步。既然沒(méi)有這樣的函數(shù),那么就創(chuàng)建一個(gè),s_gets()函數(shù)詳見(jiàn)程序清單 1.41。
程序清單1.41 s_gets()函數(shù)
1 char * s_gets(char *st, int n)
2 {
3 char *ret_value;
4 int i = 0;
5
6 ret_value = fgets(st, n, stdin);
7 if(ret_value){
8 while(st[i] != '\n' && st[i] != '\0')
9 i++;
10 if(st[i] == '\n')
11 st[i] = '\0';
12 else
13 while(getchar() != '\0')
14 continue;
15 }
16 return ret_value;
17 }
如果fgets()返回NULL,說(shuō)明讀到文件結(jié)尾或出現(xiàn)讀取錯(cuò)誤,s_gets()跳過(guò)了這個(gè)過(guò)程。其中的循環(huán):
while(st[i] != '\n' && st[i] != '\0')
i++;
遍歷字符串,直到遇到換行符或空字符。如果先遇到換行符,下面的if語(yǔ)句將其替換成空字符;如果先遇到空字符,else部分便丟棄輸入行的剩余字符,然后返回與fgets()相同的值。
盡管s_gets()用于替換fgets()已經(jīng)有了很大的改進(jìn),但還是不完美。如果遇到不合適的輸入時(shí),它毫無(wú)反應(yīng)。它丟棄多余的字符時(shí),也不通知程序也不告知用戶,請(qǐng)讀者完善。
由于fgets()將換行符放在字符串的末尾(假設(shè)輸入行不溢出),通常要與fputs()配對(duì)使用,除非該函數(shù)不在字符串末尾添加換行符。
fputs()函數(shù)接受兩個(gè)參數(shù):第1個(gè)是字符串的地址,第2個(gè)是文件指針,指明要寫(xiě)入的文件,該函數(shù)根據(jù)傳入地址找到的字符串寫(xiě)入指定的文件中。如果要顯示在計(jì)算機(jī)顯示器上,應(yīng)使用標(biāo)準(zhǔn)輸出stdout作為參數(shù)。和puts()不同的是,puts()在打印字符串時(shí),不會(huì)在其末尾添加換行符。其調(diào)用示例如下:
fputs(buf, fp);
其中,buf是字符串的地址,fp用于指定目標(biāo)文件。注意,gets()丟棄輸入中的換行符,但puts()在輸出中添加換行符。而另一方面,fgets()保留了輸入中的換行符,fputs()在輸出中不會(huì)添加換行符。
-
字符串
+關(guān)注
關(guān)注
1文章
567瀏覽量
20432
原文標(biāo)題:周立功:字符真正價(jià)值在于形成字符序列——字符串的輸入輸出
文章出處:【微信號(hào):ZLG_zhiyuan,微信公眾號(hào):ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論