本文為程序設(shè)計(jì)基礎(chǔ),本文為1.8.2 字符串常量第三點(diǎn):字符串函數(shù)。
1.字符串函數(shù)
標(biāo)準(zhǔn)C提供了一個(gè)操作字符串的接口string.h,其中的很多函數(shù)是有特殊用途的,下面將詳細(xì)介紹通過string.h導(dǎo)出的最重要的函數(shù)原型。
strlen()函數(shù)用于統(tǒng)計(jì)字符串的長度,其函數(shù)原型如下:
size_t strlen(const char *s);
//前置條件:s是一個(gè)以null結(jié)尾的字符串
//后置條件:返回值為s中的字符個(gè)數(shù)(不包括null字符)
下面的函數(shù)可以縮短字符串的長度,其中用到了strlen()。即:
1 void fit(char *string, unsigned int size)
2 {
3 if(strlen(string) > size)
4 strlen[size] = '\0';
5 }
由于該函數(shù)要改變字符串,因此函數(shù)在聲明形參string時(shí)沒有使用const限定符。當(dāng)然,也可以調(diào)用strlen(s)確定字符串s的長度,或用if語句比較兩個(gè)字符串是否相等。即:
if(strcmp(s1, s2) == 0) …
用于拼接字符串的strcat()函數(shù)接受兩個(gè)字符串作為參數(shù),其函數(shù)原型如下:
char *strcat(char *s1, char const *s2);
//前置條件:s2是一個(gè)以null結(jié)尾的字符串,s1數(shù)組的末尾還有足夠的空間容納s2的一個(gè)副本
//后置條件:s2被連接到s1中,且返回值是一個(gè)指針,該指針指向s1數(shù)組的第一個(gè)字符
該函數(shù)將第2個(gè)字符串的備份附加在第1個(gè)字符串末尾,s2字符串的第1個(gè)字符將覆蓋s1字符串末尾的空字符,并將拼接后的新字符串作為第1個(gè)字符串返回,第2個(gè)字符串不變。strcat()函數(shù)的類型是char *(即指向char的指針),strcat()函數(shù)返回第1個(gè)參數(shù),即拼接第2個(gè)字符串后的第1個(gè)字符串的地址。
由于strcat()函數(shù)無法檢查第1個(gè)數(shù)組是否能夠容納第2個(gè)字符串,如果分配給第1個(gè)數(shù)組的空間不夠大,那么多出來的字符溢出到相鄰存儲單元時(shí)就會出問題,當(dāng)然也可以用strlen()查看第1個(gè)數(shù)組的長度。注意,要給拼接后的字符串長度加1才夠空間存放末尾的空字符?;蛘哂胹trncat(),該函數(shù)的第3個(gè)參數(shù)指定了添加的最大字符數(shù),其函數(shù)原型如下:
char *strncat(char *s1, char const *s2, size_t n);
該函數(shù)將s2字符串中的n個(gè)字符拷貝到s1字符串末尾,s2字符串中的第1個(gè)字符將覆蓋s1字符串末尾的空字符。不會拷貝s2字符串中的空字符和其后的字符,并在拷貝字符的末尾添加一個(gè)空字符,該函數(shù)返回s1。
strcmp()函數(shù)要比較的是字符串的內(nèi)容,不是字符串的地址,其函數(shù)原型如下:
int strcmp(char const *s1, char const *s2);
//前置條件:s1和s2都是以null結(jié)尾的字符串
//后置條件:返回值為0,表示s1=s2;
// 返回值小于0,表示s1在詞典順序上位于s2之前;
// 返回值大于0,表示s1在詞典順序上位于s2之后
如果s1字符串在機(jī)器排序序列中位于s2字符串的后面,該函數(shù)返回一個(gè)正數(shù);如果兩個(gè)字符串相等,則返回0;如果s1字符串在機(jī)器排序序列中位于s2字符串的前面,則返回一個(gè)負(fù)數(shù)。strcmp()函數(shù)比較的是字符串("hello")不是字符('q'),所以其參數(shù)應(yīng)該是字符串。由于char的類型實(shí)際上是整數(shù)類型,因此可以使用關(guān)系運(yùn)算符比較字符。如果兩個(gè)字符串開始的幾個(gè)字符都相同會怎樣?strcmp()會依次比較每個(gè)字符,直到發(fā)現(xiàn)第1對不同的字符為止,然后返回相應(yīng)的值。比如,"apples"和"apple"只有最后一對字符不一樣("apples"的s和"apple"的空字符),由于空字符在ASCII中排第1,字符s一定在它后面,所以strcmp()返回一個(gè)正數(shù)。
最后一個(gè)例子,strcmp()比較所有的字符,不只是字母。與其說該函數(shù)按字母順序進(jìn)行比較,還不如說是按機(jī)器排序序列進(jìn)行比較,即根據(jù)字符的數(shù)值(ASCII值)進(jìn)行比較,在ASCII中,大寫字母在小寫字母前面,因此strcmp("Z", "a")返回的是負(fù)值。
在大多數(shù)情況下,strcmp()返回的具體值并不重要,只在意該值是0還是非0,即比較兩個(gè)值是否相等,或按字母排序字符串,此時(shí)需要知道比較的結(jié)果是正、負(fù)或0。假設(shè)word是存儲在char類型數(shù)組中的字符串,ch是char類型的變量。即:
if(strcmp(word, "hello") == 0) puts("bye")
if(ch == 'q' ) puts("bye")
盡管如此,不要使用ch或'q'作為strcmp()的參數(shù)。
strcmp()比較兩個(gè)字符串中的字符,直到發(fā)現(xiàn)不同的字符為止,這一過程可能會持續(xù)到字符串的末尾。而strcmp()在比較兩個(gè)字符串時(shí),可以比較字符不同的地方,也可以只比較第3個(gè)參數(shù)指定的字符數(shù)。其函數(shù)原型如下:
int strncmp(char const *s1, char const *s2, size_t n);
strcpy()函數(shù)有兩個(gè)屬性,第一,strcpy()的返回值類型為char *,該函數(shù)返回的是第1個(gè)參數(shù)的值,即一個(gè)字符的地址。第二,第1個(gè)參數(shù)不必指向數(shù)組的開始,這個(gè)屬性可用于拷貝數(shù)組的一部分。注意,strcpy()將源字符串中的空字符也拷貝在內(nèi)。
如果pts1和pts2都是指向字符串的指針,那么下面語句拷貝的是字符串的地址,而不是字符串本身。即:
pts2 = pts1;
如果希望拷貝整個(gè)字符串,可以使用strcpy()函數(shù)。其函數(shù)原型如下:
char *strcpy(char *s1, char const *s2);
//前置條件:s2是一個(gè)以null結(jié)尾的字符串,s1數(shù)組有足夠的空間容納s2的一個(gè)副本
//后置條件:s2被復(fù)制到s1,且返回值是一個(gè)指針,該指針指向s1數(shù)組的第一個(gè)字符
該函數(shù)將s2指向的字符串(包括空字符)拷貝至s1指向的位置,返回s1。即strcpy()接受2個(gè)字符串指針作為參數(shù),可以將指向源字符串的第2個(gè)指針聲明為指針、數(shù)組名或字符串常量,而指向源字符串副本的第1個(gè)指針應(yīng)指向一個(gè)數(shù)據(jù)對象,比如,數(shù)組,且對象要有足夠的空間存儲字符串的副本,通常將拷貝出來的字符串稱為目標(biāo)字符串。注意,聲明數(shù)組將分配存儲數(shù)據(jù)的空間,而聲明指針只分配存儲一個(gè)地址的空間。
strcpy()和strcat()都有同樣的問題,它們不能檢查目標(biāo)空間是否能容納源字符串的副本,因此拷貝字符串使用strncpy()更安全,該函數(shù)的第3個(gè)參數(shù)指明可拷貝的最大字符數(shù)。其函數(shù)原型如下:
char *strncpy(char *s1, char const *s2, size_t n);
該函數(shù)將s2指向的字符串拷貝至s1指向的位置,拷貝的字符不超過n,其返回值為s1。該函數(shù)不會拷貝空字符后面的字符,如果源字符串中的字符數(shù)少于n,則目標(biāo)字符串就以拷貝的空字符結(jié)尾。如果源字符串有n個(gè)或超過n個(gè)字符,就不拷貝空字符,所以拷貝的副本中不一定有空字符?;诖耍话銜設(shè)置比目標(biāo)數(shù)組大小少1,然后將數(shù)組最后一個(gè)元素設(shè)置為空字符。
這樣做的目的將確保存儲的是一個(gè)字符串,如果目標(biāo)空間能夠容納源字符串的副本,那么從源字符串拷貝的空字符便是該副本的結(jié)尾;如果目標(biāo)空間裝不下副本,則將副本最后一個(gè)元素設(shè)置為空字符。
盡管C語言允許將字符串作為一個(gè)字符數(shù)組或一個(gè)指向字符的指針,但是從更抽象的角度理解字符串將會更有意義。如果你想訪問字符串中的單個(gè)字符,則需要注意它的表現(xiàn)形式。如果將字符串當(dāng)作一個(gè)整體來看待的話,那么就可以忽略其表現(xiàn)的細(xì)節(jié),而寫出更容易理解的程序。比如:
typedef char *striing;
其目的是強(qiáng)調(diào)字符串是一個(gè)在概念上完全不同的類型,雖然string與char *類型完全相同,但它們傳遞的信息卻是不同的。如果將一個(gè)變量定義為char *類型,其底層的表示方法是指針;如果將一個(gè)變量定義為string類型,就會將該字符串作為整體看待。這樣一來,在聲明函數(shù)的形參時(shí),無論是將字符串作為數(shù)組、指針或抽象數(shù)據(jù)類型,它們都是可以互換的,其聲明方法如下:
int strlen(string cStr);
int strlen(char cStr[]);
int strlen(char *cStr);
雖然標(biāo)準(zhǔn)C提供的string.h接口提供了一系列的字符串操作函數(shù),它允許在函數(shù)調(diào)用時(shí)將字符串作為一個(gè)整體對待,但這些函數(shù)同樣要求我們了解底層的表示,即函數(shù)將分配內(nèi)存的任務(wù)留給了用戶,特別是檢測緩沖區(qū)溢出的條件。當(dāng)使用這個(gè)接口時(shí),用戶要為每個(gè)字符串的存儲負(fù)責(zé)。這種分配方式不僅增加了程序員的負(fù)擔(dān),也間接使得編碼中的錯(cuò)誤增多了。
使用gets()函數(shù)從標(biāo)準(zhǔn)輸入讀入字符串容易導(dǎo)致緩沖區(qū)溢出,而誤用strcpy()和strcat()同樣如此。因?yàn)槭褂媚承┖瘮?shù)可能造成攻擊者用格式化字符串攻擊的方法訪問內(nèi)存,甚至能夠注入代碼,所以C11版本加入了strcat_s()和strcpy_s()函數(shù),如果發(fā)生緩沖區(qū)溢出,它們會返回錯(cuò)誤。printf()、fprintf()和snprintf()這些函數(shù)都接受格式化字符串作為參數(shù),避免這類攻擊的一種簡單方法是不要將用戶提供的格式化字符串傳給這些函數(shù)。
-
字符串
+關(guān)注
關(guān)注
1文章
575瀏覽量
20468 -
C語言編程
+關(guān)注
關(guān)注
6文章
90瀏覽量
21082 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
569瀏覽量
40072 -
程序設(shè)計(jì)
+關(guān)注
關(guān)注
3文章
261瀏覽量
30352 -
周立功
+關(guān)注
關(guān)注
38文章
130瀏覽量
37555
原文標(biāo)題:周立功:字符真正價(jià)值在于形成字符序列——字符串函數(shù)
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論