之前的文章 Strings 提到過,C 語言中的字符串就是內存中的字節序列,以 NULL (‘\0’) 結尾。這個 NULL 字符至關重要,因為它使得所有的這些字符串操作函數(比如 printf(), puts(), 以及其他所有的操作字符串的函數)知道字符串在何處結尾。
下面,我們就來討論下這些字符串操作函數:截出子字符串、把若干字符串連接起來、獲取字符串長度等等。
返回字符串長度
#includesize_t strlen(const char*s);
這個函數返回一個字符串的長度(不包含 NULL 結尾符)。它是遍歷整個字符串,知道碰到 NULL 字符,所以有點耗時間。如果你需要重復獲得相同字符串的長度,最好使用 strlen() 之後,把長度值保存在一個變量中。
返回字符串中的字符個數
char *s = "Hello, world!"; // 13 characters // prints "The string is 13 characters long.": printf("The string is %d characters long.\n", strlen(s));
比較兩個字符串並且返回一個差值。
#includeint strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n);
兩個函數均是對兩個字符串進行比較。strcmp() 比較整個字符串,而 strncmp() 僅僅比較字符串的前 n 個字符。
返回的差值要說明一下:
- 如果兩個字符串相同,返回 0,否則
- 如果 s1 小於 s2,返回的是 負數,否則
- 返回的是正數
通常情況下,只要檢查返回值是不是 0 就行了,因為人們一般只關心兩個字符串是不是相同。
如果兩個字符串相等,返回0;如果 s1 小於 s2,返回 負數;如果 s1 大於 s2 返回正數。
char *s1 = "Muffin"; char *s2 = "Muffin Sandwich"; char *s3 = "Muffin"; strcmp("Biscuits", "Kittens"); // returns < 0 因為 'B' < 'K' strcmp("Kittens", "Biscuits"); // returns > 0 因為 'K' > 'B' if (strcmp(s1, s2) == 0) printf("This won't get printed because the strings differ"); if (strcmp(s1, s3) == 0) printf("This will print because s1 and s3 are the same"); if (!strcmp(s1, s3)) printf("The strings are the same!") if (!strncmp(s1, s2, 6)) printf("The first 6 characters of s1 and s2 are the same");
把兩個字符串連成一個
#includechar* strcat(const char *dest, const char *src); char* strncat(const char *dest, const char *src, size_t n);
“Concatenate”,就是連接的意思。這倆函數把持有兩個字符串,然後把它們連接起來,存儲在第一個字符串中。
值得注意的是,這倆函數都沒有考慮第一個字符串的長度問題。把一個 2M 的字符串存儲在一個 10B 的空間裡,結果一定會非常的酸爽。
一定要檢查字符串的長度問題。不然會出現意想不到的結果。
strncat() 把 src 的前 n 個字符添加到 dest 的結尾處(覆蓋 dest 結尾處的 ‘\0’) 並添加 ‘\0’。
兩個函數均返回指向 destination 字符串的指針
char dest[20] = "Hello"; char *src = ", World!"; char numbers[] = "12345678"; printf("dest before strcat: \"%s\"\n", dest); // "Hello" strcat(dest, src); printf("dest after strcat: \"%s\"\n", dest); // "Hello, world!" strncat(dest, numbers, 3); // strcat first 3 chars of numbers printf("dest after strncat: \"%s\"\n", dest); // "Hello, world!123"
在字符串中查詢字符
#includechar *strchr(char *str, int c); char *strrchr(char *str, int c);
strchr() 和 strrchr() 在字符串中查找首次或者最後出現的一個字符( “r” 是 “reverse” 的意思,從後往前找)。每個函數都會返回指向找到的字符的指針,如果沒找到,返回 NULL。
如果想找下一次出現的位置,那麼可以再次調用函數並且參數設置成上一次的返回值加 1(正向找)。或者減 1 (反向找的話)。如果正好到了字符串的末尾了,那要小心了,不然可能會溢出。
返回指向找到的字符的指針,如果沒找到,返回 NULL。
// "Hello, world!" // ^ ^ // A B char *str = "Hello, world!"; char *p; p = strchr(str, ','); // p now points at position A p = strrchr(str, 'o'); // p now points at position B // repeatedly find all occurances of the letter 'B' char *str = "A BIG BROWN BAT BIT BEEJ"; char *p; for(p = strchr(str, 'B'); p != NULL; p = strchr(p + 1, 'B')) { printf("Found a 'B' here: %s\n", p); } // output is: // // Found a 'B' here: BIG BROWN BAT BIT BEEJ // Found a 'B' here: BROWN BAT BIT BEEJ // Found a 'B' here: BAT BIT BEEJ // Found a 'B' here: BIT BEEJ // Found a 'B' here: BEEJ
拷貝一個字符串
#includechar *strcpy(char *dest, char *src); char *strncpy(char *dest, char *src, size_t n);
這倆函數從一個地址處拷貝一個字符串到另一個地址,在 src 字符串的 NULL 處停止拷貝。
strncpy() 與 strcpy() 很像,唯一的區別就是只拷貝 src 的前 n 個字符。
請注意
如果 src 沒有 n 個字符,那麼 dest 字符串將不會以 NULL 結尾。一定要小心。這個時候 strncpy() 的表現與 strcpy() 是一樣的。
可以手動給字符串添加結束字符 ‘\0’:
char s[10]; char foo = "My hovercraft is full of eels."; // more than 10 chars strncpy(s, foo, 9); // only copy 9 chars into positions 0-8 s[9] = '\0'; // position 9 gets the terminator
兩個函數均返回指向目標字符串 dest 的指針
char *src = "hockey hockey hockey hockey hockey hockey hockey hockey"; char dest[20]; int len; strcpy(dest, "I like "); // dest is now "I like " len = strlen(dest); // Be aware the differences between strlen() and sizeof() strncpy(dest+len, src, sizeof(dest)-len-1); // remember that sizeof() returns the size of the array in bytes // and a char is a byte: dest[sizeof(dest)-1] = '\0'; // terminate // dest is now: v null terminator // I like hockey hocke
返回字符串中第一個不在 (strspn()) 或者 在 (strcspn()) 指定字符串中出現的字符下標
#includesize_t strspn(char *str, const char *accept); size_t strcspn(char *str, const char *reject);
strspn() 函數告訴你 src 字符串中第一個不在 accept 字符串中出現的字符的下標。
strcspn() 函數告訴你 src 字符串中第一個在 reject 字符串中出現的字符的下標
返回字符串中第一個不在 (strspn()) 或者 在 (strcspn()) 指定字符串中出現的字符下標
char str1[] = "a banana"; char str2[] = "the bolivian navy on manuvers in the south pacific"; // str1 中第一個不在 "aeiou" 中的字符下標? n = strspn(str1, "aeiou"); // n == 1,"e" // str1 中第一個不在 "ab " 中的字符下標? n = strspn(str1, "ab "); // n == 4,"n" // str2 中第一個在 "y" 中的字符下標? n = strcspn(str2, "y"); // n = 16, "y"
在一個字符串中查找另一個字符串
#includechar *strstr(const char *str, const char *substr);
在 str 字符串中查找 substr,返回指向 substr 的指針。
返回指向 substr 的指針,或者是 NULL(如果沒找到)。
char *str = "The quick brown fox jumped over the lazy dogs."; char *p; p = strstr(str, "lazy"); printf("%s\n", p); // "lazy dogs." // p is NULL after this, since the string "wombat" isn't in str: p = strstr(str, "wombat");
分割字符串
#includechar *strtok(char *str, const char *delim);
如果有一個字符串裡面有很多分隔符,想要把這個字符串分割成一小段一小段,那麼這個函數就派上用場了。
strtok() 的使用方式有點特別,其實是有點怪異。
str 是將要被分割的字符串,第一次調用 strtok() 時,str 已經被分割成一個一個的小片段了。為了獲得更多的分割的片段,需要多次調用 strtok(),但是需要傳入 NULL。這是它詭異的地方,但是 strtok() 記住了初始傳入的 str 字符串,然後不斷的分割它。
需要注意的是,str 字符串在調用過程中會被破壞掉,所以如果想不被修改,需要傳入 str 的副本,這樣 strtok() 就不會破壞原始字符串了。
一個指向下一個 分隔片段 的指針,如果沒了,就返回 NULL。
char str[] = "Where is my bacon, dude?"; char *token; // 注意,在使用 strtok() 時,下面的 if-do-while 結構 // 會經常經常經常見到!!! // 獲取第一個token (making sure there is a first token!) if ((token = strtok(str, ".,?! ")) != NULL) { do { printf("Word: \"%s\"\n", token); // while循環繼續獲取下一個 token // (傳入 NULL 作為第一個參數) } while ((token = strtok(NULL, ".,?! ")) != NULL); } // output: Word: "Where" Word: "is" Word: "my" Word: "bacon" Word: "dude"