C-字符串、字符和字節(jié)(上)

字符串是一種重要的數(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)的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容