4. 字元串

之前我一直對字元串避而不談,不做詳細解釋,現在已經具備了必要的基礎知識,可以深入討論一下字元串了。字元串可以看作一個數組,它的每個元素是字元型的,例如字元串"Hello, world.\n"圖示如下:

圖 8.2. 字元串

字元串

注意每個字元末尾都有一個字元'\0'做結束符,這裡的\0是ASCII碼的八進製表示,也就是ASCII碼為0的Null字元,在C語言中這種字元串也稱為以零結尾的字元串(Null-terminated String)。數組元素可以通過數組名加下標的方式訪問,而字元串字面值也可以像數組名一樣使用,可以加下標訪問其中的字元:

char c = "Hello, world.\n"[0];

但是通過下標修改其中的字元卻是不允許的:

"Hello, world.\n"[0] = 'A';

這行代碼會產生編譯錯誤,說字元串字面值是隻讀的,不允許修改。字元串字面值還有一點和數組名類似,做右值使用時自動轉換成指向首元素的指針,在第 3 節 “形參和實參”我們看到printf原型的第一個參數是指針類型,而printf("hello world")其實就是傳一個指針參數給printf

前面講過數組可以像結構體一樣初始化,如果是字元數組,也可以用一個字元串字面值來初始化:

char str[10] = "Hello";

相當於:

char str[10] = { 'H', 'e', 'l', 'l', 'o', '\0' };

str的後四個元素沒有指定,自動初始化為0,即Null字元。注意,雖然字元串字面值"Hello"是隻讀的,但用它初始化的數組str卻是可讀可寫的。數組str中保存了一串字元,以'\0'結尾,也可以叫字元串。在本書中只要是以Null字元結尾的一串字元都叫字元串,不管是像str這樣的數組,還是像"Hello"這樣的字元串字面值。

如果用於初始化的字元串字面值比數組還長,比如:

char str[10] = "Hello, world.\n";

則數組str只包含字元串的前10個字元,不包含Null字元,這種情況編譯器會給出警告。如果要用一個字元串字面值準確地初始化一個字元數組,最好的辦法是不指定數組的長度,讓編譯器自己計算:

char str[] = "Hello, world.\n";

字元串字面值的長度包括Null字元在內一共15個字元,編譯器會確定數組str的長度為15。

有一種情況需要特別注意,如果用於初始化的字元串字面值比數組剛好長出一個Null字元的長度,比如:

char str[14] = "Hello, world.\n";

則數組str不包含Null字元,並且編譯器不會給出警告,[C99 Rationale]說這樣規定是為程序員方便,以前的很多編譯器都是這樣實現的,不管它有理沒理,C標準既然這麼規定了我們也沒辦法,只能自己小心了。

補充一點,printf函數的格式化字元串中可以用%s表示字元串的占位符。在學字元數組以前,我們用%s沒什麼意義,因為

printf("string: %s\n", "Hello");

還不如寫成

printf("string: Hello\n");

但現在字元串可以保存在一個數組裡面,用%s來打印就很有必要了:

printf("string: %s\n", str);

printf會從數組str的開頭一直打印到Null字元為止,Null字元本身是Non-printable字元,不打印。這其實是一個危險的信號:如果數組str中沒有Null字元,那麼printf函數就會訪問數組越界,後果可能會很詭異:有時候打印出亂碼,有時候看起來沒錯誤,有時候引起程序崩潰。