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