字串,顧名思義,是一連串的字元,也就是字元的陣列。 C 語言裡面的字串,會寫在雙引號當中。一個字串會以空字元(NULL, '\0')來做為結束。 以下是一個簡單的範例:
#include <stdio.h> int main() { char a[] = "test"; char b[] = {'t','e','s','t','\0'}; printf("%s\n",a); /* 使用 %s 印出字串 */ printf("%s\n",b); return 0; }變數 a 和 b 都是字元陣列,在記憶體中的樣子都如同以下:
a[0] (或 b[0]) 't' a[1] (或 b[1]) 'e' a[2] (或 b[2]) 's' a[3] (或 b[3]) 't' a[4] (或 b[4]) '\0'
多出來的一個位置,是用來存放代表字串結束的空字元。如果沒有留下適當的空位,會出現無法預期的結果。如果要接受使用者輸入一個字串,則可在 scanf 裡面使用「%s」:
#include <stdio.h> int main() { char str[100]; scanf("%s", str); printf("輸入的字串是:%s\n", str); return 0; }scanf 會一直讀入字元,直到遇上空白、換行,或者沒有輸入為止。 另外,我們將陣列名稱當作指標使用,因此不需要加上「&」。 當然,此處還必須假設使用者不會輸入多於 99 個字,以免超過陣列的大小。如果要對字串中的個別字元做處理,則可含括 ctype.h,裡面提供了一些判斷/轉換用的函式。 例如,islower 可以判斷一個字元是否小寫英文字母;toupper 可將小寫英文字母轉換為大寫:
#include <stdio.h> #include <ctype.h> int main() { char a[] = "ABCdef123"; int i = 0; printf("a[0]是否為大寫字母 : %d\n", islower(a[0]) ); printf("a[6]是否為數字 : %d\n", isdigit(a[6]) ); printf("原來的字串 : %s\n", a); while(a[i]!='\0'){ a[i] = toupper(a[i]); i++; } printf("新的字串 : %s\n", a); return 0; }如果需要將字串"123"轉換成整數,或者將字串"45.678"轉換成浮點數, 則可使用 stdlib.h 中的 atoi 和 atof 函式:
#include <stdio.h> #include <stdlib.h> int main() { printf("%d\n", atoi("123")); printf("%f\n", atof("45.678")); return 0; }前面的範例中提到,scanf 碰到空白就會停下來。如果希望連空白一起讀取,則可以使用 fgets 函式。 fgets 會一直讀入字元,直到碰到換行、沒有字了,或者設定的最大上限為止:
#include <stdio.h> #define SIZE 100 int main() { char str[SIZE]; fgets(str, SIZE, stdin); printf("經由 fgets 輸入的字串是:%s\n", str); return 0; }其中,第一個參數是陣列名稱,第二個參數是要讀取的字元個數(包含自動加上的'\0'), 第三個參數暫且固定為 stdin (若有興趣,可以先參考相關書籍中,關於檔案的章節, 你會發現,改變第三個參數,就可以從檔案當中讀取資料)。 經由 fgets 讀取進來的字串,會連同換行字元(就是你敲的那一下Enter)都忠實的保留; 它也會自動加上'\0',所以你不用擔心字串結束不了。getchar 函式也非常相似,他會不斷地,一個一個地讀取字元。 以下範例,會將從鍵盤輸入的字元讀取進來,一個一個地塞到 sentence 陣列當中。 直到讀到換行時,才會脫離迴圈,並將字串內容顯示出來:
#include <stdio.h> #define SIZE 1000 int main() { char c; char sentence[SIZE]; int i = 0; while( (c=getchar())!='\n' ){ sentence[i] = c; i++; } printf("輸入的字串是:%s\n", sentence); return 0; }但當各位執行這個範例時,也許會發現到多印了一些奇怪的字, 這是因為 getchar 不會幫你在尾巴加入 '\0',這個 '\0' 需要由各位自己加入。 例如,可以在讀取結束後加上「sentence[i] = '\0'」。在以往的範例中,我們使用 printf 和 scanf,將資料印到螢幕(標準輸出)上,或者從鍵盤(標準輸入)讀取資料。 其實,也可以從陣列進行輸入與輸出,使用的函式是 sprintf 和 sscanf:
#include <stdio.h> #define SIZE 100 int main() { char a[SIZE]; char b[] = "123 45.6"; int c; double d; sprintf(a, "%d %.4f\n~abcde~%d~fghijk~\nggg", 1234, 567.89, 101112); printf("a 的內容: %s\n", a); sscanf(b, "%d %lf", &c, &d); printf("i: %d, j: %f\n", c, d); return 0; }如果需要知道字串的長度,可以使用 strlen 函式,必須先含括 string.h:
#include <stdio.h> #include <string.h> int main() { char msg[1234] = "Happy birthday to "; printf("length of \"%s\" is: %d\n", msg, strlen(msg)); return 0; }字串的複製與連接並不是使用「=」或「+」,而是 strcpy / strcat 函式:
#include <stdio.h> #include <string.h> int main() { char msg[1234] = "Happy birthday to "; char nameA[] = "John."; char name[100]; strcpy(name, nameA); printf("The name is: %s\n", name); strcat(msg, name); printf("%s\n", msg); return 0; }這些函式都位於 string.h 當中,他們會把後面的字串,複製或連接到前面的字串上。 strcpy 和 strcat 會複製/連接整個字串, strncpy 和 strncat 則多了一個參數,讓你指定只需要複製/連接字串的前 n 個字元。 除了 strncpy 以外的函式,都會幫你自動加上 '\0'。另外,使用這四個函數的時候,你必須自己保證被複製/連接的目標,可以裝的下新的字串。還記得指標的神奇範例嗎?上次是用字元指標「char *」指向整數區塊; 這次我們用整數指標「int *」來指向字元的區塊:
#include <stdio.h> #include <string.h> int main() { char str[1024]={'\0'}; int *ptr, i; strcpy(str, "Hello World!"); ptr = (int*)str; for(i=0;ptr[i]!=0;i++){ printf("%d\n", ptr[i]); } return 0; }你會發現,印出來的正好是那些看似莫名其妙的數字。 這個現象,跟整數在記憶體裡的存放方式有關。若有興趣,可以到網路上搜尋"little endian"這個關鍵詞。透過 strrev 函式,可以幫你把字串反轉:
#include <stdio.h> #include <string.h> int main() { char msg[1234] = "Hello World"; printf("原字串: %s\n", msg); strrev(msg); printf("新字串: %s\n", msg); return 0; }透過 strstr 函式,可以幫你尋找子字串。 如果找到子字串, strstr 會回傳指向子字串開頭的指標; 如果找不到,則會回傳 NULL (代表指標指向空的位置,各位可以先將它視為 false):
#include <stdio.h> #include <string.h> int main() { char msg[] = "Hello World"; char str[8]; char *pos; printf("Please enter a string: "); scanf("%s", str); pos = strstr(msg, str); if(pos){ printf("Found, %s\n", pos); } else { printf("Not Found.\n"); } return 0; }Question: 如何知道字串開始位置的陣列索引值?(Hint: 指標加減)字元和整數的關係是非常密切的。例如,「A」在電腦裡面會被表示為「0100 0001」,相當於十進位的 65;「a」在電腦裡面會被表示為「0110 0001」,相當於十進位的 97。 這種編碼方式稱為 ASCII (American Standard Code for Information Interchange)。 如果要知道某個字母對應的數字為何,或者某個數字對應的字母為何,則方法如下:
#include <stdio.h> int main() { printf("%c: %d\n", 'A', 'A'); printf("%d: %c\n", 100, 100); return 0; }所以,字串的大小比較,其實就是其中每個字母所對應的數字的大小比較:#include <stdio.h> int main() { printf("Is %c greater than %c: %d\n", 'Z', 'B', 'Z'>'B'); printf("Is %c greater than %c: %d\n", 'A', 'b', 'A'>'b'); return 0; }而如果需要比較兩個字串的大小,則可使用 strcmp 或 strncmp 函式。
strcmp 函式會比較前後兩個字串,如果:
- 相等則傳回0
- 前面大於後面,則傳回一個正整數
- 前面小於後面,則傳回一個負整數
#include <stdio.h> #include <string.h> int main() { char a[] = "test"; char b[] = "test"; char c[] = "Test"; char d[] = "ABcd"; printf("%s v.s. %s: %d\n", a, b, strcmp(a,b) ); printf("%s v.s. %s: %d\n", a, c, strcmp(a,c) ); printf("%s v.s. %s: %d\n", c, d, strcmp(c,d) ); printf("%s v.s. %s: %d\n", d, c, strcmp(d,c) ); return 0; }strncmp 也非常相似,但是你可以只比對前 n 個字元。#include <stdio.h> #include <string.h> int main() { char a[] = "test1234"; char b[] = "test5678"; printf("%s v.s. %s: %d\n", a, b, strncmp(a,b,4) ); printf("%s v.s. %s: %d\n", a, b, strncmp(a,b,5) ); return 0; }Question: 如何從字串的中間開始進行 strncmp 的比較?(Hint: 指標加減)如果需要存放一堆字串,則必須使用二維陣列,使用方法如下:
#include <stdio.h> int main() { char suit[][10] = {"Clubs", "Diamonds", "Hearts", "Spades"}; printf("%s", suit[0]); return 0; }事實上,二維陣列是一個指標陣列(存放一堆指標的一維陣列),這些指標會指向某些一維陣列的開頭, 以上述範例來說,可以理解如下:
所以,下面的範例也有同樣的效果:#include <stdio.h> int main() { char *suit[4] = {"Clubs", "Diamonds", "Hearts", "Spades"}; printf("%s", suit[0]); return 0; }