上一個練習中,我們學習了如何創(chuàng)建基本的數組,以及數組怎么樣映射為字符串。這個練習中我們會更加全面地展示數組和字符串的相似之處,并且深入更多內存布局的知識。
這個練習向你展示了C只是簡單地將字符串儲存為字符數組,并且在結尾加上'\0'(空字符)。你可能在上個練習中得到了暗示,因為我們手動這樣做了。下面我會通過將它與數字數組比較,用另一種方法更清楚地實現它。
#include <stdio.h>
int main(int argc, char *argv[])
{
int numbers[4] = {0};
char name[4] = {'a'};
// first, print them out raw
printf("numbers: %d %d %d %d\n",
numbers[0], numbers[1],
numbers[2], numbers[3]);
printf("name each: %c %c %c %c\n",
name[0], name[1],
name[2], name[3]);
printf("name: %s\n", name);
// setup the numbers
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
// setup the name
name[0] = 'Z';
name[1] = 'e';
name[2] = 'd';
name[3] = '\0';
// then print them out initialized
printf("numbers: %d %d %d %d\n",
numbers[0], numbers[1],
numbers[2], numbers[3]);
printf("name each: %c %c %c %c\n",
name[0], name[1],
name[2], name[3]);
// print the name like a string
printf("name: %s\n", name);
// another way to use name
char *another = "Zed";
printf("another: %s\n", another);
printf("another each: %c %c %c %c\n",
another[0], another[1],
another[2], another[3]);
return 0;
}
在這段代碼中,我們創(chuàng)建了一些數組,并對數組元素賦值。在numbers中我們設置了一些數字,然而在names中我們實際上手動構造了一個字符串。
當你運行這段代碼的時候,你應該首先看到所打印的數組的內容初始化為0值,之后打印初始化后的內容:
$ make ex9
cc -Wall -g ex9.c -o ex9
$ ./ex9
numbers: 0 0 0 0
name each: a
name: a
numbers: 1 2 3 4
name each: Z e d
name: Zed
another: Zed
another each: Z e d
$
你會注意到這個程序中有一些很有趣的事情:
numbers的每個元素被打印時,它們都輸出0。names的每個元素被打印時,只有第一個元素'a'顯示了,因為'a'是特殊字符不會顯示。names,打印出了"a",因為它在初始化表達式中的'a'字符之后都用'\0'填充,是以'\0'結尾的正確的字符串。numbers已經設置好了,看看names字符串是如何正確打印出我的名字的。char name[4] = {'a'},或者第44行的char *another = "name"。前者不怎么常用,你應該將后者用于字符串字面值。注意我使用了相同的語法和代碼風格來和整數數組和字符數組交互,但是printf認為name是個字符串。再次強調,這是因為對C語言來說,字符數組和字符串沒有什么不同。
最后,當你使用字符串字面值時你應該用char *another = "Literal"語法,它會產生相同的東西,但是更加符合語言習慣,也更省事。
C中所有bug的大多數來源都是忘了預留出足夠的空間,或者忘了在字符串末尾加上一個'\0'。事實上,這些bug是非常普遍并且難以改正的,大部分優(yōu)秀的C代碼都不會使用C風格字符串。下一個練習中我們會學到如何徹底避免C風格字符串。
使這個程序崩潰的的關鍵就是拿掉字符串結尾的'\0'。下面是實現它的一些途徑:
name的初始化表達式。name[3] = 'A',于是它就沒有終止字符了。'a','a','a','a'},于是就有過多的'a'字符,沒有辦法給'\0'留出位置。試著想出一些其它的辦法讓它崩潰,并且在Valgrind下想通常一樣運行這個程序,你可以看到具體發(fā)生了什么,以及錯誤叫什么名字。有時Valgrind并不能發(fā)現你犯的錯誤,則需要移動聲明這些變量的地方看看是否能找出錯誤。這是C的黑魔法的一部分,有時變量的位置會改變bug。
numbers的元素,之后用printf一次打印一個字符,你會得到什么編譯器警告?names執(zhí)行上述的相反操作,把names當成int數組,并一次打印一個int,Valgrind會提示什么?name嗎?你如何用黑魔法實現它?name轉換成another的形式,看看代碼是否能正常工作。