1. 程式人生 > >字串、字元和位元組

字串、字元和位元組

1. 字串

1.1 字串基礎

字串就是一串零個或多個字元,並且以一個位模式為全0的NUL位元組結尾。NUL位元組是字串的終止符,但是它本身不是字串的一部分,所以字串的長度並不包括NUL位元組。

標頭檔案string.h包含了使用字串函式所需的原型和宣告。

1.2 字串長度

字串的長度就是它所包含的字元個數。strlen可求字串長度,返回值是size_t型別,該型別在標頭檔案stddef.h中定義,是一個無符號整數型別。在表示式中使用無符號數可能會導致不可預料的結果,例如,下面兩個表示式看上去是相等的:

if (strlen(x) >= strlen(y))....

if (strlen(x) - strlen(y) >= 0)...

但事實上它們是不相等的,第1條語句按照預想的工作,但第2條語句的結果將永遠是真。strlen的結果是個無符號數,所以操作符>=左邊的表示式也將是無符號數,而無符號數絕不可能是負的。

表示式中如果同時 包含了有符號數和無符號數,可能會產生奇怪的結果。如果把strlen的返回值強制轉換為int,就可以消除這個問題。

尋找一種更好的演算法比改良一種差勁的演算法更有效率,複用已經存在的軟體比重新開發一個效率更高。

 

1.3 函式的返回值

strcpy和strcat都返回它們第一個引數的一份拷貝。就是一個指向目標字元陣列的指標。由於它們返回這種型別的值,所以可以巢狀地呼叫這些函式,如:

strcat(strcpy(dst, a), b);

但是這種巢狀呼叫的風格較可讀性更佳的拆開的風格在功能上並無優勢。

1.4 字串比較

int strcmp(char const *s1, char const *s2);

如果s1小於s2,strcmp函式返回一個小於零的值,如果s1大於s2,函式返回一個大於零的值,如果兩個字串相等,函式返回零。

把這種返回值當做布林值進行測試是一種壞風格,因為它具有三個截然不同的結果,所以,更好的方法是把這個返回值與零進行比較。

1.5 長度受限的字串函式

和strcpy一樣,strncoy把源字串的字元複製到目標陣列,但是它總是正好向dst寫入len個字元。如果strlen(src)的值小於len,dst陣列就用額外的NUL位元組填充到len長度。如果strlen(src)的值大於或等於len,那麼只有len個字元被複制到dst中。注意!它的結果將不會以NUL位元組結尾。

所以,strncpy呼叫的結果可能不是一個字串,因為字串必須以NUL位元組結尾。如果在一個需要字串的地方(例如strlen函式的引數)使用了一個不是以NUL結尾的字元序列,strlen()會一直找到發現一個NUL為止,如果函式試圖訪問系統分配給這個程式以外的記憶體範圍,程式就會崩潰。這個問題只有當你使用strncpy函式建立字串,然後或者對它們使用str開頭的庫函式,或在printf中使用%s格式列印它們時才發生。在使用不受限的函式之前,你首先必須確定字串實際上是以NUL位元組結尾的。

而strncat總是在結果字串後面新增一個NUL位元組。strncat不管目標引數除去原先存在的字串後留下的空間夠不夠。

1.6 字串查詢基礎

1.6.1 查詢一個字元

在一個字串中查詢一個特定字元最容易的方法是使用strchr和strrchr函式。

char * strchr(char const *str, int ch);

char * strrchr(char const *str, int ch);

1.6.2 查詢任何幾個字元

char * strpbrk(char const *str, char const *group);

1.6.3 查詢一個子串

char * strstr(char const *s1, char const *s2);

標準庫中不存在strrstr或strrpbrk

1.6.4 查詢一個字串字首

 

1.6.5 查詢標記

char * strtok(char * str, char const *sep);

1.7 記憶體操作

根據定義,字串由一個NUL位元組結尾,所以字串內部不能包含任何NUL字元。但是,非字串資料內部包含零值的情況並不罕見,你無法使用字串函式來處理這種型別的資料,因為當它們遇到第1個NUL位元組時將停止工作。

此時,我們可以使用另一組相關的函式,它們的操作與字串函式類似,但這些函式能夠處理任意的位元組序列。

void *memcpy(void * dst, void const *src, size_t length);

void *memmove(void *dst, void const *src, size_t length);

void *memcmp(void const * a, void const * b, size_t length);

void *memchr(void const *a , int ch, size_t length);

void *memset(void *a, int ch, size_t length);

每個原型都包含一個顯示的引數說明需要處理的位元組數,但和strn帶頭的函式不同,它們在遇到NUL位元組時不會停止操作。

memcpy從src的起始位置複製length個位元組到dst的記憶體起始位置。你可以用這個方法複製任何型別的值,第3個引數指定複製值的長度(以位元組計)。如果src和dst以任何形式出現了重疊,結果的未定義的。

如果兩個陣列都是整型陣列,不需要使用強制型別轉換,因為在函式的原型中,引數的型別是void *指標,任何型別的指標都可以轉換為viod*型指標。

2. 警告的總結

  • 在應該使用有符號數的表示式中使用strlen函式(strlen返回無符號)
  • 在表示式中混用有符號數和無符號數
  • 使用strcpy函式把一個長字串複製到一個較短的陣列中,導致溢位。
  • 使用strcat函式把一個長字串新增到一個數組中,導致陣列溢位
  • 把strcmp函式的返回值當做布林進行測試
  • 把strcmp函式的返回值與1和-1進行比較
  • 使用並非以NUL位元組結尾的字元序列
  • 使用strncpy函式產生不以NUL位元組結尾的字串
  • 把strncpy函式和strxxx族函式混用
  • 忘了strtok函式會修改它所處理的字串
  • strtok函式是不可再入的

3.程式設計提示的總結

  • 不要試圖自己編寫功能相同的函式來取代庫函式
  • 使用字元分類和轉換函式可以提高函式的移植性