“字符串是一種重要的數(shù)據(jù)類型,但是C語言并沒有顯式的字符串?dāng)?shù)據(jù)類型,因為字符串以字符串常量的形式出現(xiàn)或者存儲于字符數(shù)組中。字符串常量適用于那些程序不會進行修改的字符串。所有其他字符串必須存儲于字符數(shù)組或動態(tài)分配的內(nèi)存中。本文描述處理字符串和字符的庫函數(shù),以及一些相關(guān)的,具有類似能力的函數(shù)。”
01
—
字符串基礎(chǔ)
首先,我們了解下字符串的基礎(chǔ)知識。字符串就是一串零個或多個字符,并且以一個位模式為全0的NUL字節(jié)結(jié)尾。因此,字符串包含的字符內(nèi)部不能出現(xiàn)NUL字節(jié)。這個限制很少會引起問題,因為NUL字節(jié)并不存在與它相關(guān)聯(lián)的可打印字符,這也是它被選為終止符的原因。NUL字節(jié)是字符串的終止符,但它本身并不是字符串的一部分,所以字符串的長度并不包括NUL字節(jié)。
02
—
字符串的長度
字符串的長度就是它所包含的字符個數(shù)。我們很容易通過對字符進行計數(shù)來計算字符串的長度,下面程序就是這樣做的。
#include <stddef.h>size_t strlen(char const *string){????int?length;????for?(length?=?0;?*string++?!=?'\0';?)????? ? length += 1;????return?length;}
這種實現(xiàn)方法說明了處理字符串所使用的的處理過程的類型。但是,事實上你極少需要編寫字符串函數(shù),因為標(biāo)準(zhǔn)庫所提供的函數(shù)通常能完成這些任務(wù)。不過,如果你還是希望自己編寫一個字符串函數(shù),請注意標(biāo)準(zhǔn)保留了所有以str開頭的函數(shù)名,用于標(biāo)準(zhǔn)庫將來的拓展。
03
—
不受限制的字符串函數(shù)
最常用的字符串函數(shù)都是“不受限制”的,就是說它們只是通過尋找字符串參數(shù)結(jié)尾的NUL字節(jié)來判斷它的長度。這些函數(shù)一般都指定一塊內(nèi)存用于存放結(jié)果字符串。在使用這些函數(shù)時,程序員必須保證結(jié)果字符串不會溢出這塊內(nèi)存。這節(jié)將做詳細(xì)討論。
復(fù)制字符串
????用于復(fù)制字符串的函數(shù)是strcpy,它的原型如下所示:
char?*strcpy(char *dst, char const *src);
這個函數(shù)把參數(shù)src字符串復(fù)制到dst參數(shù),如果src和dst在內(nèi)存中出現(xiàn)重疊,其結(jié)果是未定義的。由于dst參數(shù)將進行修改,所以它必須是個字符數(shù)組或者是一個指向動態(tài)分配內(nèi)存的指針,不能使用字符串常量。
目標(biāo)參數(shù)的的以前內(nèi)容將被覆蓋并丟失。即使新的字符串比dst原先的內(nèi)存更短,由于新字符串是以NUL字節(jié)結(jié)尾的,所以老字符串最后剩余的幾個字符也會被有效地刪除。
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|
第一個NUL字節(jié)后面的幾個字符無法被字符串函數(shù)訪問,因此從實際角度來看,它們已經(jīng)是丟失的了。
警告:
你必須保證目標(biāo)字符數(shù)組的空間足以容納需要復(fù)制的字符串,如果字符串比數(shù)組長,多余的字符仍被復(fù)制,它們將覆蓋原先存儲于數(shù)組后面的內(nèi)存空間的值,strcpy無法解決這個問題,因為它無法判斷目標(biāo)字符數(shù)組的長度。例如:
char message[] = "Original messgae";...strcpy(message, "A different message");
第二個字符串太長了,無法容納于message數(shù)組中。因此,strcpy函數(shù)將侵占數(shù)組后面的部分內(nèi)存空間,改寫原先恰好存儲在那里的變量。如果你在使用這個函數(shù)前確保目標(biāo)函數(shù)足以容納源字符串,就可以避免大量調(diào)試工作。
2.連接字符串
要想把一個字符串添加(連接)到另一個字符串的后面,你可以使用strcat函數(shù)。它的原型如下:
char?*strcat(char?*dst,?char?const *src);
strcat要求dst參數(shù)原先已經(jīng)包含了一個字符串(可以是空字符串),它找到這個字符串的末尾,并把src字符串的一份拷貝添加到這個位置。如果src和dst的位置發(fā)生重疊,其結(jié)果是未定義的。
下面是它的常見用法,
char?name[]?=?"Jim";strcpy(message, "Hello ");strcat(message, name);strcat(message,?",?how are you?");
每個strcat函數(shù)的字符串參數(shù)都被添加到原先存于message數(shù)組的字符串后面,其結(jié)果是下面這個字符串:
Hello Jim, how are you?
3.函數(shù)的返回值
strcpy和strcat都返回它們第一個參數(shù)的一份拷貝,就是一個指向目標(biāo)字符數(shù)組的指針,由于它們都是返回這種類型的值,所以你可以嵌套地調(diào)用這些函數(shù),如下所示:
strcat(strcpy(dst, a), b);
strcpy首先執(zhí)行。它把字符串從a復(fù)制到dst并返回dst。然后這個返回值稱為strcat函數(shù)的第一個參數(shù),strcat把b添加到dst后面。
這種嵌套調(diào)用的風(fēng)格較之下面這種可讀性更佳的風(fēng)格在功能上并無優(yōu)勢。
strcpy(dst, a);strcat(dst, b);
事實上,這些函數(shù)的的絕大多數(shù)調(diào)用中,它們的返回值只是被簡單地忽略。
4.字符串比較
比較兩個字符串涉及對兩個字符串對應(yīng)的字符逐個比較,直到發(fā)現(xiàn)不匹配為止。那個最先不匹配的字符中較“小”(在字符集中的序數(shù)較?。┑哪莻€字符所在的字符串被認(rèn)為小于另外一個字符串。如果其中一個字符串是另一個字符串的前面一部分,那么它也被認(rèn)為小于另外一個字符串,因為它的NUL結(jié)尾字節(jié)出現(xiàn)得更早。這種比較被稱為“詞典比較”,對于只包含大寫字母或只包含小寫字母的字符比較,這種比較過程所給出的結(jié)果總是和我們?nèi)粘K玫淖帜疙樞虻谋容^相同。
庫函數(shù)strcmp用于比較兩個字符串,它的原型如下:
int strcmp(char const *s1, char const *s2);
如果s1小于s2,函數(shù)返回一個小于零的值,如果s1大于s2,函數(shù)返回一個大于零的值。如果兩個字符串相等,函數(shù)就返回零。
04
—
長度受限的字符串函數(shù)
標(biāo)準(zhǔn)庫還包含了一些函數(shù),它們以一種不同的方式處理字符串。這些函數(shù)接受一個顯式的長度參數(shù),用于限定進行復(fù)制或比較的字符數(shù)。這些函數(shù)提供了一種方便的機制,可以防止難以預(yù)料的長字符從它們的目標(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寫入len個字符。如果strlen(src)的值小于len,dst就用額外的NUL字符填充到len長度。如果strlen(src)的值大于等于len,那么只有l(wèi)en個字符被復(fù)制到dst中。注意!它的結(jié)果將不會以NUL字節(jié)結(jié)尾。
警告:
strncpy調(diào)用的結(jié)果可能不是一個字符串,因為字符串必須以NUL字節(jié)結(jié)尾。如果在一個需要字符串的地方(如strlen函數(shù)的參數(shù))使用了一個不是以NUL字節(jié)結(jié)尾的字符序列,會發(fā)生什么情況呢?strlen函數(shù)將無法知道NUL字節(jié)是沒有的,所以它會繼續(xù)查找,直到它發(fā)現(xiàn)一個NUL字節(jié)為止。或許找了幾百個字符才找到,而strlen函數(shù)的這個返回值從本質(zhì)上說是一個隨機數(shù)?;蛘?,如果函數(shù)試圖訪問系統(tǒng)分配給這個程序以外的內(nèi)存范圍,程序就會崩潰。
盡管strncat也是一個長度受限的函數(shù),但它和strncpy存在不同之處。它從src中最多復(fù)制len個字符到目標(biāo)數(shù)組后面。但是,strncat總是在結(jié)果字符串后面添加一個NUL字節(jié),且它不會像strncpy那樣對目標(biāo)數(shù)組用NUL字節(jié)進行填充。注意目標(biāo)數(shù)組中原先的字符串并沒有算在strncat的長度中。strncat最多向目標(biāo)數(shù)組復(fù)制len個字符(再加一個結(jié)尾的NUL字節(jié)),它不會管目標(biāo)參數(shù)除去原先字符串后剩下的空間夠不夠。
最后,strncmp也用于比較兩個字符串,但它最多比較len個字節(jié)。如果兩個字符串在第len個字符之前存在不相等的字符,這個函數(shù)就像strcmp一樣停止比較,返回結(jié)果。如果兩個字符串的前l(fā)en個字符相等,函數(shù)就返回零。
05
—
字符串查找
標(biāo)準(zhǔn)庫中存在許多函數(shù),它們用各種不同的方法查找字符串。這樣各種各樣的工具給了碼手很大的靈活性。
1.查找一個字符
在一個字符串中查找一個特定字符最容易的方法是使用strchr和strrchr函數(shù),它們的原型如下:
char *strchr(char const *str, int ch);char?*strrchr(char const *str, int ch);
注意它們的第2個參數(shù)是一個整型值。但是,它包含了一個字符值。strchr在字符串str中查找字符ch第1次出現(xiàn)的位置,找到后函數(shù)返回一個指向該位置的指針。如果該字符串中不存在該字符,函數(shù)就返回一個NULL指針。strrchr的功能和strchr基本一致,只是它返回的是一個指向該字符串該字符最后一次出現(xiàn)的位置。
2.查找任何幾個字符
strpbrk是個更為常見的函數(shù)。它并不是查找某個特定的字符,而是查找任何一組字符第一次在字符串中出現(xiàn)的位置。它的原型如下:
char *strpbrk(char const *str, char const *group);
這個函數(shù)返回一個指向str中第一個匹配group中任何一個字符的字符位置。如果未找到匹配,函數(shù)返回一個NULL指針。
如下代碼:
char?string[20]?=?"Hello?there,?honey.";char *ans;ans?=?strpbrk(string,?"aeiou");
ans指向的位置是string+1,因為這個位置是第二個參數(shù)中的字符第一次出現(xiàn)的位置。和前面一樣,這個函數(shù)也是區(qū)分大小寫的。
3.查找一個子串
為了在字符串中查找一個子串,可以使用strstr函數(shù),它的原型如下:
char?*strstr(char?const?*s1,?char const *s2);
這個函數(shù)s1中查找整個s2第一次出現(xiàn)的起始位置,并返回一個指向該位置的指針。如果s2并沒有完整地出現(xiàn)在s1的任何地方,函數(shù)將返回一個NULL指針。如果第二個參數(shù)是一個空字符串,函數(shù)就返回s1。
標(biāo)準(zhǔn)庫中并不存在strrstr和strrpbrk。不過,如果你需要它們,它們是很容易實現(xiàn)的。