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