本文部分內容參考了C Primer Plus(Fifth Edition)
C語言字符串表示
字符串是C語言中最常用也是最重要的數據類型,但是C語言沒有專門提供這種類型。因為字符串由字符組成,所以聲明字符串,我們用字符數組。字符數組是字符串的變量表示方法。純字符數組和字符串的區別和聯系就是:字符串是一個以'\0'結尾的字符數組。因此,我們聲明一個字符數組char ch[32]實際上它最多只能存儲31個可顯示字符,最后一個字符是'\0',它是字符串結尾的標志。
字符串還有一種表示方法,那就是字符串常量(字符串字面量)。例如printf("%s","King and Queen");這個表達式語句中"King and Queen"就是字符串常量。實際上,它也是一個元素為字符常量的數組,這個數組內容為(char []){'K','i','n','g',' ','a','n','d',' ','Q','u','e','e','n','\0'};千萬注意別忘了'\0'。
因為字符串常量如"Anytime"可以表示這個字符串(准確說是元素為字符常量的數組)的首地址。因此,我們可以用指針來操作字符串。我們可以這樣聲名:
char * chptr = "Anytime";
但不能這樣:
char * chptr; *chptr = "Anytime";
因為*chptr表示chptr所指向的地址上面的內容,如果這個指針未初始化,那么這就是個很危險的操作,指針有可能亂指向內存空間,如果指向的是系統文件,它就會修改系統文件。即使指針已經初始化,我們也不應該這樣做,原因有兩個:
1.這樣做可能導致溢出(超出了安全的內存空間)
2.會更改不應該更改的內容(詳見這篇文章,它解釋得很好)
因此,指針操作字符串常量不安全,一般只用來傳輸字符串變量的地址(內容為字符變量的數組)。
字符串基本輸入
scanf()雖然有專門的%s來輸入字符串,但它的終止條件是遇到如空格,換行符等空白字符。不如說它是用來處理單詞輸入的。而處理長字符串輸入,最早是用gets(),當它讀到換行符時丟棄'\n'並結束輸入,它很好用(對於很早以前來說),但我不希望大家掌握它,盡管很多隨便的程序都在用它。
char cha[5]; gets(cha);
這個函數真的非常好用,直接把數組首地址代進去就行了。你可否注意到一個嚴重的缺陷?這個函數不知道這個數組的大小,也就是不知道它最多只能接受多少字符的輸入,這就會導致溢出!有一些UNIX系統的代碼大量使用了gets(),使得黑客有機會通過這個漏洞編寫程序將垃圾數據寫入系統,導致系統癱瘓,這就是流行於這些UNIX計算機之間的蠕蟲病毒。
我推薦大家使用fgets()函數,這個函數使用起來比gets()安全,但更加麻煩。
char * cha[16]; fgets(cha, 16, stdin); //fgets(名稱,大小,讀取文件) //如果要用這個函數從鍵盤讀取,請在讀取文件的地方用上stdin
傳入的大小為n,它就最多讀取n - 1個字符或遇到換行符時終止。例如,上面的代碼運行后我輸入"1234567890123456"后,cha = "123456789012345"。這個函數看起來很完美,但十全十美的東西是不存在的。fgets()的缺陷在於它讀到換行符時保存了換行符!下面是C Primer Plus第五版的有關程序示例:
此時,我們很迫切想編寫一個函數,讓它丟棄fgets()保存的換行符及后面的無效字符。如果不丟棄無效字符,就會導致后面的語句誤讀了緩沖區,就像初學字符輸入時輸入一個字符回車后再要輸入一個字符,可還沒有輸入就已經執行到后面去了。我們把我們自己編寫的函數取名為s_gets(),我們讓它的返回值和fgets()的返回值一樣。我們編寫的函數代碼如下:
1 char * s_gets(char * sptr, int size){ 2 int i = 0; //i表示讀取項數 3 char * re; //re返回和fgets()一樣的數值 4 re = fgets(sptr,size,stdin); 5 if(re){ //如果re != NULL 6 while( (sptr[i] != '\n') && (sptr[i] != '\0') ) //讀取sptr[i]直到讀到'\0'或'\n' 7 i++; 8 if(sptr[i] == '\n') //如果讀到的是'\n' 9 sptr[i] = '\0'; //把它變成'\0' 10 else 11 while( ( getchar() ) != '\n' ) //如果讀到的是別的東西,一直讀到'\n',防止后面的語句誤讀 12 continue; 13 } 14 return re; 15 }
如果日后需要進行安全的字符串輸入,用這個函數就OK了。運用這個函數的示例運行:
字符串基本輸出
printf()函數提供了一個接近於完美的字符串輸出,而且,它還可以直接輸出數字(如%d,%u等)。printf()函數的通用性很強,因此,如果不想使用其它的字符串輸出函數,一定要記住這個。相信即使剛學C的初學者也知道這個函數的用法。
puts()函數簡潔易用,直接給出字符串的地址就行了。需要注意的是,puts()函數在字符串輸出后會加上'\n',所以puts()和gets()以及上面我們自己寫的s_gets()配套使用。
char cha[16] = "Memory"; puts(cha);
fputs()函數主要和fgets()配套,需要提醒的是,這兩個函數不僅可以用在標准I/O上,還可以進行文件處理,而且一般是用在文件處理上的。fputs()函數需要兩個參數,第一個參數給出字符串地址,第二個參數給出輸出位置。因為它和fgets()配套使用,所以它在輸出字符串之后不加上'\n'。
char * cha[16] = "Memory\n"; fputs(cha,stdin);
下面給出C Primer Plus第五版中有關的例程:
自己編寫自定義的字符串輸入輸出函數!
你也可以自己編寫你自己的輸入輸出函數,而且,假設你編寫的函數沒有什么太大的錯誤,這些函數比上面所提到的大部分函數都更加可靠和靈活。我們可以使用getchar()和putchar()來完成字符串的輸入輸入功能。
下面是我寫的函數,讀者在讀完后也應該自己動手寫寫,說不定以后還能用上呢!
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define SIZE 16 5 6 char * ud_gets(char * st, int size){ //輸入 7 int i = 0; //計數器 8 while( ( ( st[i] = getchar() ) != '\n') && (i < size - 1) ) 9 i++;//如果讀到非'\n'字符或未超出限定范圍,則繼續讀取 10 while( (i > size - 1) && (getchar() != '\n')) 11 continue;//如果讀取超過限定范圍且后面還有字符,則丟棄 12 st[i] = '\0'; //結束讀取 13 return st; 14 } 15 16 char * ud_puts(char * st, int ad_enter){ //輸出 17 int i = 0; 18 while(putchar(st[i]) != '\0') //如果沒有讀到字符串結尾就繼續讀 19 i++; 20 if(ad_enter) //如果加上'\n'的開關被打開 21 putchar('\n'); //輸出換行符 22 return st; 23 } 24 25 int main(int argc, char * argv[]){ 26 char cha[SIZE]; 27 ud_gets(cha,SIZE); 28 ud_puts(cha,0); 29 getch(); 30 return 0; 31 }
運行結果: