“字符串是一種重要的數(shù)據(jù)類型,但是C語(yǔ)言并沒(méi)有顯式的字符串?dāng)?shù)據(jù)類型作儿,因?yàn)樽址宰址A康男问匠霈F(xiàn)或者存儲(chǔ)于字符數(shù)組中。字符串常量適用于那些程序不會(huì)進(jìn)行修改的字符串馋劈。所有其他字符串必須存儲(chǔ)于字符數(shù)組或動(dòng)態(tài)分配的內(nèi)存中攻锰。本文描述處理字符串和字符的庫(kù)函數(shù),以及一些相關(guān)的妓雾,具有類似能力的函數(shù)娶吞。”
01
—
字符串基礎(chǔ)
首先,我們了解下字符串的基礎(chǔ)知識(shí)械姻。字符串就是一串零個(gè)或多個(gè)字符妒蛇,并且以一個(gè)位模式為全0的NUL字節(jié)結(jié)尾。因此楷拳,字符串包含的字符內(nèi)部不能出現(xiàn)NUL字節(jié)绣夺。這個(gè)限制很少會(huì)引起問(wèn)題,因?yàn)镹UL字節(jié)并不存在與它相關(guān)聯(lián)的可打印字符欢揖,這也是它被選為終止符的原因陶耍。NUL字節(jié)是字符串的終止符,但它本身并不是字符串的一部分浸颓,所以字符串的長(zhǎng)度并不包括NUL字節(jié)物臂。
02
—
字符串的長(zhǎng)度
字符串的長(zhǎng)度就是它所包含的字符個(gè)數(shù)旺拉。我們很容易通過(guò)對(duì)字符進(jìn)行計(jì)數(shù)來(lái)計(jì)算字符串的長(zhǎng)度,下面程序就是這樣做的棵磷。
#include <stddef.h>size_t strlen(char const *string){????int?length;????for?(length?=?0;?*string++?!=?'\0';?)????? ? length += 1;????return?length;}
這種實(shí)現(xiàn)方法說(shuō)明了處理字符串所使用的的處理過(guò)程的類型蛾狗。但是,事實(shí)上你極少需要編寫(xiě)字符串函數(shù)仪媒,因?yàn)闃?biāo)準(zhǔn)庫(kù)所提供的函數(shù)通常能完成這些任務(wù)沉桌。不過(guò),如果你還是希望自己編寫(xiě)一個(gè)字符串函數(shù)算吩,請(qǐng)注意標(biāo)準(zhǔn)保留了所有以str開(kāi)頭的函數(shù)名留凭,用于標(biāo)準(zhǔn)庫(kù)將來(lái)的拓展。
03
—
不受限制的字符串函數(shù)
最常用的字符串函數(shù)都是“不受限制”的偎巢,就是說(shuō)它們只是通過(guò)尋找字符串參數(shù)結(jié)尾的NUL字節(jié)來(lái)判斷它的長(zhǎng)度蔼夜。這些函數(shù)一般都指定一塊內(nèi)存用于存放結(jié)果字符串。在使用這些函數(shù)時(shí)压昼,程序員必須保證結(jié)果字符串不會(huì)溢出這塊內(nèi)存求冷。這節(jié)將做詳細(xì)討論。
復(fù)制字符串
????用于復(fù)制字符串的函數(shù)是strcpy窍霞,它的原型如下所示:
char?*strcpy(char *dst, char const *src);
這個(gè)函數(shù)把參數(shù)src字符串復(fù)制到dst參數(shù)匠题,如果src和dst在內(nèi)存中出現(xiàn)重疊,其結(jié)果是未定義的但金。由于dst參數(shù)將進(jìn)行修改韭山,所以它必須是個(gè)字符數(shù)組或者是一個(gè)指向動(dòng)態(tài)分配內(nèi)存的指針,不能使用字符串常量冷溃。
目標(biāo)參數(shù)的的以前內(nèi)容將被覆蓋并丟失钱磅。即使新的字符串比dst原先的內(nèi)存更短,由于新字符串是以NUL字節(jié)結(jié)尾的似枕,所以老字符串最后剩余的幾個(gè)字符也會(huì)被有效地刪除续搀。
char?messgae[]?=?"Original?messgae";...strcpy(message,?"Different");
順利執(zhí)行strcpy之后,數(shù)組將包含下面內(nèi)容:
|'D'|'i'|'f'|'e'|'r'|'e'|'n'|'t'|0|'e'|'s'|'s'|'a'|'g'|'e'|0|
第一個(gè)NUL字節(jié)后面的幾個(gè)字符無(wú)法被字符串函數(shù)訪問(wèn)菠净,因此從實(shí)際角度來(lái)看,它們已經(jīng)是丟失的了彪杉。
警告:
你必須保證目標(biāo)字符數(shù)組的空間足以容納需要復(fù)制的字符串毅往,如果字符串比數(shù)組長(zhǎng),多余的字符仍被復(fù)制派近,它們將覆蓋原先存儲(chǔ)于數(shù)組后面的內(nèi)存空間的值攀唯,strcpy無(wú)法解決這個(gè)問(wèn)題,因?yàn)樗鼰o(wú)法判斷目標(biāo)字符數(shù)組的長(zhǎng)度渴丸。例如:
char message[] = "Original messgae";...strcpy(message, "A different message");
第二個(gè)字符串太長(zhǎng)了侯嘀,無(wú)法容納于message數(shù)組中另凌。因此,strcpy函數(shù)將侵占數(shù)組后面的部分內(nèi)存空間戒幔,改寫(xiě)原先恰好存儲(chǔ)在那里的變量吠谢。如果你在使用這個(gè)函數(shù)前確保目標(biāo)函數(shù)足以容納源字符串,就可以避免大量調(diào)試工作诗茎。
2.連接字符串
要想把一個(gè)字符串添加(連接)到另一個(gè)字符串的后面工坊,你可以使用strcat函數(shù)。它的原型如下:
char?*strcat(char?*dst,?char?const *src);
strcat要求dst參數(shù)原先已經(jīng)包含了一個(gè)字符串(可以是空字符串)敢订,它找到這個(gè)字符串的末尾王污,并把src字符串的一份拷貝添加到這個(gè)位置。如果src和dst的位置發(fā)生重疊楚午,其結(jié)果是未定義的昭齐。
下面是它的常見(jiàn)用法,
char?name[]?=?"Jim";strcpy(message, "Hello ");strcat(message, name);strcat(message,?",?how are you?");
每個(gè)strcat函數(shù)的字符串參數(shù)都被添加到原先存于message數(shù)組的字符串后面矾柜,其結(jié)果是下面這個(gè)字符串:
Hello Jim阱驾, how are you?
3.函數(shù)的返回值
strcpy和strcat都返回它們第一個(gè)參數(shù)的一份拷貝把沼,就是一個(gè)指向目標(biāo)字符數(shù)組的指針啊易,由于它們都是返回這種類型的值,所以你可以嵌套地調(diào)用這些函數(shù)饮睬,如下所示:
strcat(strcpy(dst, a), b);
strcpy首先執(zhí)行租谈。它把字符串從a復(fù)制到dst并返回dst。然后這個(gè)返回值稱為strcat函數(shù)的第一個(gè)參數(shù)捆愁,strcat把b添加到dst后面割去。
這種嵌套調(diào)用的風(fēng)格較之下面這種可讀性更佳的風(fēng)格在功能上并無(wú)優(yōu)勢(shì)。
strcpy(dst, a);strcat(dst, b);
事實(shí)上昼丑,這些函數(shù)的的絕大多數(shù)調(diào)用中呻逆,它們的返回值只是被簡(jiǎn)單地忽略。
4.字符串比較
比較兩個(gè)字符串涉及對(duì)兩個(gè)字符串對(duì)應(yīng)的字符逐個(gè)比較菩帝,直到發(fā)現(xiàn)不匹配為止咖城。那個(gè)最先不匹配的字符中較“小”(在字符集中的序數(shù)較小)的那個(gè)字符所在的字符串被認(rèn)為小于另外一個(gè)字符串呼奢。如果其中一個(gè)字符串是另一個(gè)字符串的前面一部分宜雀,那么它也被認(rèn)為小于另外一個(gè)字符串,因?yàn)樗腘UL結(jié)尾字節(jié)出現(xiàn)得更早握础。這種比較被稱為“詞典比較”辐董,對(duì)于只包含大寫(xiě)字母或只包含小寫(xiě)字母的字符比較,這種比較過(guò)程所給出的結(jié)果總是和我們?nèi)粘K玫淖帜疙樞虻谋容^相同禀综。
庫(kù)函數(shù)strcmp用于比較兩個(gè)字符串简烘,它的原型如下:
int strcmp(char const *s1, char const *s2);
如果s1小于s2苔严,函數(shù)返回一個(gè)小于零的值,如果s1大于s2孤澎,函數(shù)返回一個(gè)大于零的值届氢。如果兩個(gè)字符串相等,函數(shù)就返回零亥至。
04
—
長(zhǎng)度受限的字符串函數(shù)
標(biāo)準(zhǔn)庫(kù)還包含了一些函數(shù)悼沈,它們以一種不同的方式處理字符串。這些函數(shù)接受一個(gè)顯式的長(zhǎng)度參數(shù)姐扮,用于限定進(jìn)行復(fù)制或比較的字符數(shù)絮供。這些函數(shù)提供了一種方便的機(jī)制,可以防止難以預(yù)料的長(zhǎng)字符從它們的目標(biāo)數(shù)組溢出茶敏。
這些函數(shù)的原型如下壤靶,和它們不受限制的版本一樣,如果源參數(shù)與目標(biāo)參數(shù)發(fā)生重疊惊搏,strncpy和strncat的結(jié)果就是未定義的贮乳。
char?*strncpy(char?*dst,?char?const?*src,?size_t?len);char?*strncat(char?*dst,?char?const?*src,?size_t len);int?strncmp(char?const *s1, char const *s2, size_t len);
和strcpy一樣,strncpy把源字符串的字符復(fù)制到目標(biāo)數(shù)組恬惯。然而向拆,它總是正好向dst寫(xiě)入len個(gè)字符。如果strlen(src)的值小于len酪耳,dst就用額外的NUL字符填充到len長(zhǎng)度浓恳。如果strlen(src)的值大于等于len,那么只有l(wèi)en個(gè)字符被復(fù)制到dst中碗暗。注意颈将!它的結(jié)果將不會(huì)以NUL字節(jié)結(jié)尾。
警告:
strncpy調(diào)用的結(jié)果可能不是一個(gè)字符串言疗,因?yàn)樽址仨氁訬UL字節(jié)結(jié)尾晴圾。如果在一個(gè)需要字符串的地方(如strlen函數(shù)的參數(shù))使用了一個(gè)不是以NUL字節(jié)結(jié)尾的字符序列,會(huì)發(fā)生什么情況呢噪奄?strlen函數(shù)將無(wú)法知道NUL字節(jié)是沒(méi)有的死姚,所以它會(huì)繼續(xù)查找,直到它發(fā)現(xiàn)一個(gè)NUL字節(jié)為止勤篮≈剩或許找了幾百個(gè)字符才找到,而strlen函數(shù)的這個(gè)返回值從本質(zhì)上說(shuō)是一個(gè)隨機(jī)數(shù)叙谨。或者保屯,如果函數(shù)試圖訪問(wèn)系統(tǒng)分配給這個(gè)程序以外的內(nèi)存范圍手负,程序就會(huì)崩潰涤垫。
盡管strncat也是一個(gè)長(zhǎng)度受限的函數(shù),但它和strncpy存在不同之處竟终。它從src中最多復(fù)制len個(gè)字符到目標(biāo)數(shù)組后面蝠猬。但是,strncat總是在結(jié)果字符串后面添加一個(gè)NUL字節(jié)统捶,且它不會(huì)像strncpy那樣對(duì)目標(biāo)數(shù)組用NUL字節(jié)進(jìn)行填充榆芦。注意目標(biāo)數(shù)組中原先的字符串并沒(méi)有算在strncat的長(zhǎng)度中。strncat最多向目標(biāo)數(shù)組復(fù)制len個(gè)字符(再加一個(gè)結(jié)尾的NUL字節(jié))喘鸟,它不會(huì)管目標(biāo)參數(shù)除去原先字符串后剩下的空間夠不夠匆绣。
最后,strncmp也用于比較兩個(gè)字符串什黑,但它最多比較len個(gè)字節(jié)崎淳。如果兩個(gè)字符串在第len個(gè)字符之前存在不相等的字符,這個(gè)函數(shù)就像strcmp一樣停止比較愕把,返回結(jié)果拣凹。如果兩個(gè)字符串的前l(fā)en個(gè)字符相等,函數(shù)就返回零恨豁。
05
—
字符串查找
標(biāo)準(zhǔn)庫(kù)中存在許多函數(shù)嚣镜,它們用各種不同的方法查找字符串。這樣各種各樣的工具給了碼手很大的靈活性橘蜜。
1.查找一個(gè)字符
在一個(gè)字符串中查找一個(gè)特定字符最容易的方法是使用strchr和strrchr函數(shù)菊匿,它們的原型如下:
char *strchr(char const *str, int ch);char?*strrchr(char const *str, int ch);
注意它們的第2個(gè)參數(shù)是一個(gè)整型值。但是扮匠,它包含了一個(gè)字符值捧请。strchr在字符串str中查找字符ch第1次出現(xiàn)的位置,找到后函數(shù)返回一個(gè)指向該位置的指針棒搜。如果該字符串中不存在該字符疹蛉,函數(shù)就返回一個(gè)NULL指針。strrchr的功能和strchr基本一致力麸,只是它返回的是一個(gè)指向該字符串該字符最后一次出現(xiàn)的位置可款。
2.查找任何幾個(gè)字符
strpbrk是個(gè)更為常見(jiàn)的函數(shù)。它并不是查找某個(gè)特定的字符克蚂,而是查找任何一組字符第一次在字符串中出現(xiàn)的位置闺鲸。它的原型如下:
char *strpbrk(char const *str, char const *group);
這個(gè)函數(shù)返回一個(gè)指向str中第一個(gè)匹配group中任何一個(gè)字符的字符位置。如果未找到匹配埃叭,函數(shù)返回一個(gè)NULL指針摸恍。
如下代碼:
char?string[20]?=?"Hello?there,?honey.";char *ans;ans?=?strpbrk(string,?"aeiou");
ans指向的位置是string+1,因?yàn)檫@個(gè)位置是第二個(gè)參數(shù)中的字符第一次出現(xiàn)的位置。和前面一樣立镶,這個(gè)函數(shù)也是區(qū)分大小寫(xiě)的壁袄。
3.查找一個(gè)子串
為了在字符串中查找一個(gè)子串,可以使用strstr函數(shù)媚媒,它的原型如下:
char?*strstr(char?const?*s1,?char const *s2);
這個(gè)函數(shù)s1中查找整個(gè)s2第一次出現(xiàn)的起始位置嗜逻,并返回一個(gè)指向該位置的指針。如果s2并沒(méi)有完整地出現(xiàn)在s1的任何地方缭召,函數(shù)將返回一個(gè)NULL指針栈顷。如果第二個(gè)參數(shù)是一個(gè)空字符串,函數(shù)就返回s1嵌巷。
標(biāo)準(zhǔn)庫(kù)中并不存在strrstr和strrpbrk萄凤。不過(guò),如果你需要它們晴竞,它們是很容易實(shí)現(xiàn)的蛙卤。