第九章 字串 (String)
9-1 字串的基本語法 當我們需要處理姓名、地址等文字資料時,最常用的資料形態,就是字串了。 9-1 字串的基本語法 當我們需要處理姓名、地址等文字資料時,最常用的資料形態,就是字串了。 字串是指一串字元,一般的姓名、地址、物品名稱等等,任何由文字所構成的資料,都可以看成是字串資料。 字串可以看成是一種特殊的字元陣列,用一個 ASCII 碼值為 0 的特殊字元來做為字串結束的符號,在本章中我們把它簡稱為字串的結尾零。
9-1-1 字串宣告與字元陣列 宣告一個字元陣列 char name[4]={'W','a','n','g'}; 9-1-1 字串宣告與字元陣列 宣告一個字元陣列 char name[4]={'W','a','n','g'}; 這是一個四個元素的字元陣列,但不是字串。它在記憶體中的內容如下:
例如: char sname[5]={'W','a','n','g','\0'}; 注意字串的結尾零 '\0' 是一個字元,其 ASCII 碼值為 0,也就是第二章2-3節脫序字元所說的零值符號,它和數字符號 '0' 字元 (ASCII碼值為48)是不同的
可以用字串常數來設定初值: char sname[5]="Wang"; 字串常數就是用雙引號 " 框起來的文字,這個例子和上面的寫法效果一樣,但寫起來比較方便。但是要注意如果字串本身含有脫序字元如 " 等符號,應該寫成 \" 才對,所以 "The symbol \" means inch" 是表示 The symbol " means inch。 這個 sname 字串的長度是 4,我們可以叫用字串函數 strlen (sname) 來求出,但是實際上這個字串總共佔了 5 個位元組的記憶體空間。如果我們把它看成是字元陣列,那麼字元陣列的大小應該用 sizeof(sname) 來求出,也就是 5。
設定字串的初值,也常用字元指標的寫法,例如: char *sname="Wang"; 表示 sname 是一字元指標,指向一字串。這種寫法通常用於內容不會改變的訊息字串。 它在記憶體中的內容如下:
如果雙引號裡面沒有任何字串,就是所謂的空字串,或是稱為虛指標,也可以用 NULL 常數來代表。 例如: char *sname=""; 此 sname 為空字串,字串的長度為 0。 另外一點要注意的是,'A' 和 "A" 是不一樣的,前者是一個字元,佔一個位元組;後者是字串,佔兩個位元組。
9-1-2 字串的初值設定 (1) char msg1[5]={'C','h','e','n','\0'}; 9-1-2 字串的初值設定 想要宣告字串順便設定初值,可以有幾種不同的做法: (1) char msg1[5]={'C','h','e','n','\0'}; (2) char msg2[5]="Chen"; (3) char msg3[ ]={'C','h','e','n','\0'}; (4) char msg4[ ]="Chen"; (5) char *msg5="Chen";
【語法練習 9-1-1】 (1)試用字元陣列宣告長度為10的字串s ______________________________ (1)試用字元陣列宣告長度為10的字串s ______________________________ (2)試宣告字串s,並設定初值為hello
9-1-3 字串的輸出 將字串輸出到螢幕,通常有以下幾種做法: (1) puts 例如 9-1-3 字串的輸出 將字串輸出到螢幕,通常有以下幾種做法: (1) puts 例如 puts("this is a demo string"); puts(msg1); 等等,只顯示字串的內容。 (2) printf 的 %s 格式 例如 printf("this is a demo string"); printf("Content of msg1 : %s",msg1); 等等,除了顯示字串的內容,還可附加其他文字,所以使用較方便。 (3) 逐一字元輸出
(3) 逐一字元輸出 如果想要將字串內部的字元逐一輸出,可以用 for (i=0; i<strlen(i); i++) putchar(msg1[i]); 或是 i=0; while (msg1[i] != '\0') putchar(msg1[i++]);
【語法練習 9-1-2】 (1)試將下列字串輸出到螢幕 char s1[]="test"; char *s2="test2"; (1)試將下列字串輸出到螢幕 char s1[]="test"; char *s2="test2"; ______________________________
9-1-4 字串的輸入 從鍵盤輸入字串,通常也有幾種方式: (1) gets 例如 char msg1[81],fname[13]; 9-1-4 字串的輸入 從鍵盤輸入字串,通常也有幾種方式: (1) gets 例如 char msg1[81],fname[13]; gets(msg1); puts("input filename:); gets(fname); 用 gets 函數來讀取字串,只能用敲下 Enter 鍵(即'\n')做為結束。 因此以列為單位的資料,就可以用 gets 來讀取。
(2) scanf 的 %s 格式 例如 char msg1[81],fname[13]; scanf("%s",msg1); printf("input filename:"); scanf("%s",fname); 用 scanf 函數來讀取字串,只要遇到空白(' ')、Tab鍵('\t')、或 Enter 鍵('\n'),就視為資料結束。 因此以文字為單位的資料,例如英文的姓(first name)和名(last name)等單字,就可以用 scanf 來讀取。
(3) 逐一字元輸入 如果想要將字串的字元逐一輸入,可以用 char s[81]; int i; i=0; while ((s[i]=getchar()) != '\n') i++; s[i]='\0'; printf("string=%s , length=%d",s,i); 請注意最後要加上結尾零 '\0',才能做為字串;而 i 值剛好是字串長度。
【語法練習 9-1-3】 (1)試用字元陣列宣告長度為 20 的字串 s ______________________________ (1)試用字元陣列宣告長度為 20 的字串 s ______________________________ (2)由鍵盤輸入字串給 s (3)輸出 s 字串
字串: 字元陣列與字元指標 (1)使用字元陣列: 宣告時可以直接設定初值,例如: char s[10]="Trouble95"; 宣告時可以直接設定初值,例如: char s[10]="Trouble95"; 但是程式中不可以用 = 直接存入值,例如: char a[10]; a="chensam"; 這是錯誤的語法,因為 a 是陣列名稱,代表起始位址,是一個常數。 只能用 strcpy 函數將字串拷貝存入,例如: strcpy(a,"chensam"); 因此,宣告字元陣列時,就應預留足夠的元素個數來存字串,例如代表檔案名稱的字串,至少應留 13 個字元 (檔名 8 + 小數點 1 + 延伸檔名 3 + 字串結尾 1),如果代表螢幕的一列,就應至少留 81 個字元才夠。
(2)使用字元指標: 宣告時也可以直接設定初值,例如: char *s="Trouble95"; 系統自動計算出所需的字元長度,自動配置空間。也可以程式中用 = 直接將指標指向字串,例如: char *a; a="chensam"; 有時候我們常在字串初值宣告加上 const 的指定,例如: const char *msg="DEMO string"; 表示 msg 變數的值是固定不變的,如果程式中有所改變,系統會發出錯誤訊息來警告。
但是如果沒有設初值,例如: char *a; 在進行字串處理的動作之前,應先安排 a 指標所指的記憶體空間,否則 strcpy(a,"chensam"); 或 *a='A"; 這些存入動作會產生不可預期的嚴重錯誤。
我們可以事先讓指標指向字元陣列,例如: char m[81]; char *a=m; 或是用第八章的動態記憶體位配置來預留空間,例如: char *a; a= (char *) malloc(81); 請記住!為指標安排所需的資料空間,是程式設計者的責任!
9-2 常用的字串函數 這一節我們介紹一些常用的字串函數,大部份有關字串的處理,都會運用到這些函數,我們先討論一些最常用的函數。 9-2 常用的字串函數 這一節我們介紹一些常用的字串函數,大部份有關字串的處理,都會運用到這些函數,我們先討論一些最常用的函數。 使用這些字串函數,應先加入一些標頭檔的宣告: #include <string.h> 才能順利地呼叫成功。
9-2-1 字串長度 strlen 函數原型為: unsigned int strlen (char *str); 此函數用於求字串的長度,字串長度是指結尾零之前的字元個數,其一般用法如下例: char name[]="sjsmit"; int len; len = strlen(name); printf("the length of %s is %d",name,len); 傳入一個字串,傳回整數長度。本例的字串長度為 6。
9-2-2 字串拷貝 strcpy 函數原型為: char *strcpy (char *dest, char *source); 此函數用於將一字串拷貝給另一字串,其一般用法如下例: char source[10]="DOS",dest[10]="Windows"; printf("before strcpy(): source=%s dest=%s\n“ ,source,dest); strcpy(dest,source); printf("after strcpy(): source=%s dest=%s\n“ 傳入兩個字串,將 source 來源字串拷貝給 dest 目的字串,要注意參數的次序,目的字串在前,來源字串在後。
9-2-3 字串合併 strcat 函數原型為: char *strcat (char *dest, char *source); 此函數用於將一字串的拷貝加到另一字串的後面,得到合併的字串,其一般用法如下例: char *s1="Chen",*s2="HongShan",*s3; s3 = strcat(s1,s2); printf("s1=%s s2=%s s3=%s",s1,s2,s3); 將 s2 拷貝到 s1 的後面,使 s1 變成合併的字串,傳回值就是指向合併字串的指標。
9-2-4 字串比較 strcmp 函數原型為: int strcmp (char *s1, char *s2); 此函數用於比較兩字串的大小,傳回值為整數值: 若 s1 小於 s2 ,則傳回值 < 0 若 s1 等於 s2 ,則傳回值 ==0 若 s1 大於 s2 ,則傳回值 > 0 字串的大小是從頭個字元開始,依 ASCII 碼值的大小來比較,因此: "abc" > "AAA" "abc" > "ABC" "abc" > "ab"
通常我們用 strcmp 函數來比較兩字串是否相同,其一般用法如下例: char *answer="taipei",guess[40]; printf("the most famious city in Taiwan ?"); gets(guess); if (strcmp(guess,answer) == 0) printf("You got it !"); else printf("I am sorry !");
最後,我們把重要的字串函數整理如下: #include <string.h>
9-3 字串的應用
9-3-1 字串陣列 例如用二維陣列來宣告字串陣列: char name[10][11]; 9-3-1 字串陣列 例如用二維陣列來宣告字串陣列: char name[10][11]; 安排了 10 個字串,每個字串最多 10 個字元,再加上一個結尾零的字元 也可以設定初始值: char name[10][11]= { "Peter", "Michale", "Sam", "Marry", "Tom", "Bob", "Kelly", "Ben", "Dongdong", "Monkey" }
可以改用指標陣列來做,處理資料也較快速。 char *name[10]= { "Peter", "Michale", "Sam", "Marry", "Tom", "Bob", "Kelly", "Ben", "Dongdong", "Monkey" };
【範例 9-3-1】字串陣列的初值設定 我們常使用字串陣列來儲存名單,將一些人名或物品名稱內建在程式裡。 4 #include <string.h> 5 6 char *country[10]= { "China", 7 "America", 8 "Japan", 9 "Taiwan", 10 NULL 11 };
17 i=0; 18 while (strcmp(country[i],NULL) != 0) 19 i++; 20 n=i; 21 printf("There are %d countries :\n",n); 22 for (i=0; i<n; i++) 23 printf("the %dth country is %s\n", i,country[i]);
【範例 9-3-2】 先輸入字串資料的個數,再依序輸入字串陣列的各項元素,印出字串個數及內容。 4 #include <string.h> 5 6 main () 7 { 8 static char name[10][21]; 9 int i,n;
11 printf("input N :"); 12 scanf("%d%*c",&n); /* use "%*c" to skip '\n' character */ 14 for (i=0; i<n; i++) { 15 printf("input %dth string: ",i); 16 gets(name[i]); 17 } 18 19 printf("=== total %d strings ===\n",n); 20 for (i=0; i<n; i++) 21 puts(name[i]);
【範例 9-3-3】 連續輸入一字串,存入字串陣列中,直接遇到 END 為止,印出有效字串個數及內容。 3 #include <string.h> 4 5 main () 6 { 7 static char name[10][21]; 8 int i,n;
10 printf("input strings end by END:\n"); 12 gets(name[i]); 13 while (strcmp(name[i],"END") != 0) { 14 i++; 15 gets(name[i]); 16 } 17 18 n=i; 19 printf("=== total %d strings ===\n",n); 20 for (i=0; i<n; i++) 21 puts(name[i]);
9-3-2 命令列引數 所謂的命令列(command line),就是我們下 DOS 命令時,DOS 提示符號(如 A:\> 或 C:\TC>)出現的那一列。有些命令會代入一些引數,例如: copy a:ttt.c b:xxx.c 這些名稱 copy 與 a:ttt.c 與 b:xxx.c,就是所謂的命令列引數,可以看成是字串來用。 到目前為止,我們自己寫的C程式會產生可執行檔,例如由原始程式 try.c 產生 try.exe,我們可以在 DOS 的命令列直接敲下 try 來執行它。 那麼,try 程式所附的引數如何傳入程式內呢?那就要修改 main 函數的宣告啦!
main () 是表示程式不帶任何命令列引數。 改成 main (int argc, char *argv[]) 或是 main (int argc, char **argv) 其中 argc 是引數個數,argv 則是字串陣列,其元素指向各引數字串。
例如在 DOS 命令列執行 ex9-3-4 aaa bbb (假設你的程式是 ex9-3-4.exe) 那麼程式執行時,DOS 傳給 main 的 argc 與 argv 值如下: argc 為 3,表示共 3 個引數。 argv[0] 指向 "ex9-3-4" 字串,也就是程式名稱(其實會得到完整的路徑檔名)。 argv[1] 指向 "aaa" 字串。 argv[2] 指向 "bbb" 字串。
【範例 9-3-4】命令列引數的輸入 4 main (int argc, char *argv[]) 5 { 6 int i; 7 5 { 6 int i; 7 8 printf("arguments list:\n"); 9 for (i=0; i<argc; i++) 10 printf("%s\n",argv[i]); 11 }
9-3-3 字串與數值轉換 (1) atoi,itoa atoi 函數是將 ASCII 字串轉換成 int 整數 9-3-3 字串與數值轉換 (1) atoi,itoa atoi 函數是將 ASCII 字串轉換成 int 整數 反過來,要將數值變成字串,就使用 itoa 函數 (2) atol,ltoa atol 函數是將 ASCII 字串轉換成 long 長整數 ltoa 函數是將 long 長整數轉換成 ASCII 字串 (3) atof,fcvt atof 函數是將 ASCII 字串轉換成浮點數 fcvt 函數是將浮點數轉換成 ASCII 字串
(4) 指定格式的字串輸出 sprintf 此函數用法類似 printf,其差異是 printf 輸出到螢幕,而 sprintf 將資料轉成指定格式後輸出到字串,傳回值為整數值但通常不使用
(1) atoi 函數是將 ASCII 字串轉換成 int 整數,其函數原型如下: #include <stdlib.h> int atoi (const char *s ); 用法的範例如下: 若 int num; char *str="1234"; num=atoi(str); 則整數變數 num 的值就會是 1234 了。
要將數值變成字串,就使用 itoa 函數,其函數原型如下: #include <stdlib.h> char *itoa (int value, char *str, int radix); 其中 value 是待轉換的數值,str 是轉換後的字串,radix 是數值的基底,傳回值和 str 一樣的指標。記得要替 str 預留空間來存結果字串。 用法的範例如下: int num=12345; char str[17]; itoa(num,str,10); 則 str 的值就會是字串 "12345" 了
(2) atol 函數是將 ASCII 字串轉換成 long 長整數,其函數原型如下: #include <stdlib.h> long atol (const char *s ); 用法的範例如下: long num; char *str="987654321"; num=atol(str);
ltoa 函數是將 long 長整數轉換成 ASCII 字串,其函數原型如下: #include <stdlib.h> char *ltoa (long value,char *str,int radix); 用法的範例如下: long num; char str[33]; ltoa(num,str,10);
(3) atof 函數是將 ASCII 字串轉換成浮點數,其函數原型如下: #include <stdlib.h> double atof (const char *s ); 用法的範例如下: float num; char *str="12345.67"; num=atof(str);
fcvt 函數是將浮點數轉換成 ASCII 字串,其函數原型如下: #include <stdlib.h> char *fcvt (double value,int ndigit, int *dec, int *sign); 其中 ndigit 是有效位數,*dec 表示小數點在結果字串的第幾個字元,*sign 表示是否為 >= 0 的結果。 用法的範例如下: double num; char *str; int dec,sign,ndigit=5; num=9.876; str=fcvt(num,ndigit,&dec,&sign); printf("string=%s decimal place=%d sign=%d",str,dec,sign);
(4) 指定格式的字串輸出 sprintf 此函數用法類似 printf,其差異是 printf 輸出到螢幕,而 sprintf 將資料轉成指定格式後輸出到字串,傳回值為整數值但通常不使用,其一般用法如下例: #include <stdio.h> char buffer[256]; int i; float c; sprintf(buffer,"%d inch = %f cm",i,c); puts(buffer);
【範例 9-4-1】 設計一程式,將下列字串由小排到大後,逐一印出。 Ford,Nissan,Mitsubishi,Toyota,Honda,Benz,BMW 3 #include <string.h> 4 void print_all (char *[ ], int ); 6 main () 7 { 8 static char *company[]= {"Ford","Nissan","Mitsubishi","Toyota", 9 "Honda","Benz","BMW" 10 }; 11 int i,j,n; 12 char *tp;
14 n=sizeof(company)/sizeof(char *); 15 printf("Before sort:\n"); 16 print_all(company,n); 17 for (j=n-2; j>=0; j--) 18 for (i=0; i<=j; i++) 19 if (strcmp(company[i],company[i+1])>0) 20 { tp=company[i]; 21 company[i]=company[i+1]; 22 company[i+1]=tp; 23 } 24 printf("after sort:\n"); 25 print_all(company,n);
28 void print_all (char *s[], int n) 29 { 30 int i; 31 32 for (i=0; i<n; i++) 33 puts(s[i]); 34 }