文章程式碼顯示

2018年2月4日 星期日

《筆記》C語言 - 06_4:字元陣列與字串、字串與 scanf & printf、scanf 的缺陷(排除字元)

字元陣列與字串

截至目前為止,我們都只使用到「存放整數的陣列」,然而陣列是可以存放任何型別的資料集合

現在讓我們來討論使用「存放字元的陣列」來儲存「字串」

對於 C 而言,字串(例如 "Hello" ) 是一個存放各個字元的陣列,存放字串的陣列我們就稱為字元陣列

當我們宣告一個陣列為 char 型別,就指明該陣列為字元陣列,其可以使用字串(用雙引號包裹文字,如下方的 "Hello" 就是字串)來指定陣列的初始值

: 將字母用單引號包裹起來就成了字元,用雙引號包裹起來成了字串


char string[] = "Hello";

該程式語句會將陣列 string 的元素初始值設定為字串 "Hello" 的 "各個字元" ,且該陣列的大小將由編譯器根據字串的長度自動決定

字串 "Hello" 包含五個英文字母加上一個自動生成的空字元(null character) 

空字元又稱空白字元或結束字元,因此陣列 string 實際上具有 6 個元素。空白字元為 '\0' 

在C語言裡,字串都會「自動」以空白字元做為結束

: 只有宣告為 char 並加上雙引號的陣列才會自動生成空白字元,其他型態(type)的陣列並不會有。若將"一個字元"硬是用雙引號括起來,會使得該陣列具有兩個元素,其中一個為自動產生的空白字元 '\0'

字元陣列也可以用各別的字元來給予初始值,以下宣告與上方程式語句的宣告是等價的

char string[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

因為字串實際上是字元所組成的陣列,所以我們可以直接使用索引(index)來直接存取字串中各別的字元,例如 string1[0] 是字元 'H'

我們可以用 scanf 配合轉換指定詞 %s 直接從鍵盤輸入"一個字串"到字元陣列中。例如

char string2[20];

產生了一個能存放 19 個字元加上 1 個空字元的字元陣列

scanf( "%s", string2 );

上述程式語句會從鍵盤讀入一個字串到 string2 當中

注意 當我們使用 scanf 將字串讀入時,並不需要像一般的變數一樣加上 "&" 符號。(原因在指標章節敘述) 因為 & 符號實際上是用來告訴 scanf 讀進來的字元要放入哪個「地址」

scanf 函式將會一直讀入字元,直到遇上了空白、tab、new line 或 end-of-file 指示器為止

注意我們在宣告 string2 時表明只能存放 19 個字元,假若使用者輸入的字串大於 19 個字元,則成是很有可能會因此當機。所以在使用 scanf 讀入字串時,以下的寫法是比較好的

scanf( "%19s", string2 );

在轉換指定詞內加入數字,可以限定能夠輸入的字串長度,超過 19 字元的話 scanf 就不會將超過的部分寫入記憶體地址內

同樣的, printf 也可以配合 %s 來加以輸出,例如

printf( "%s\n", string2 );

如同 scanf , printf 也不會檢查字元陣列到底有多大,它會一直列印此陣列的各個字元,直到遇到空字元 ('\0\) 為止

#include "stdio.h"

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 char string1[] = "Hello";
 char string2[20];

 int size = sizeof(string1)/sizeof(string1[0]);
 printf( "string1[] size : %d\n\n", size );

 printf( "Enter a string : " );
 scanf( "%19s", string2 );

 printf( "string1 is %s\n", string1 );
 printf( "string2 is %s\n", string2 );
 printf( "string2 is %19s\n\n", string2 );


 printf( "Print string1 word by word \n" );

 for( int i = 0; i < size ; i++){
  printf( "%c\n", string1[i] );
 }

 printf( "Another way(smarter) \n" );

 for( int i = 0; string1[i] != '\0' ; i++){
   printf( "%c\n", string1[i] );
  }

}



使用 pinrtf 在轉換指定詞%後方加上數字可以限制列印字串長度,同時顯示的結果會靠右對齊。

scanf 的缺陷(排除字元)

事實上前文講到的 "使用 scanf 來讀入字串" 具有一個致命的缺陷

#include "stdio.h"

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 char string1[] = "Hello";
 char string2[20];

 int size = sizeof(string1)/sizeof(string1[0]);
 printf( "string1[] size : %d\n\n", size );

 printf( "Enter a string : " );
 scanf( "%19s", string2 );

 printf( "string1 is %s\n", string1 );
 printf( "string2 is %s\n", string2 );
 printf( "string2 is %19s\n\n", string2 );


 printf( "Print string1 word by word \n" );

 for( int i = 0; i < size ; i++){
  printf( "%c\n", string1[i] );
 }

 printf( "Another way(smarter) \n" );

 for( int i = 0; string1[i] != '\0' ; i++){
   printf( "%c\n", string1[i] );
  }

}



之前的例子我們輸入的字串為「 WYJlearning 」並且它可以成功在螢幕上顯示。但如果我們換成輸入字串「 WYJ learning 」時會出現一個致命的錯誤,它只有印出「WYJ」 三個字(如下圖),空格後方的文字都不見了



其原因我們在前一篇有用幾個字帶過

「scanf 函式將會一直讀入字元,直到遇上了空白、tab、new line 或 end-of-file 指示器為止」

遇到空白就會停止? 這樣的限制可真是令人頭疼。但我們可以用一些技巧來破解這函式的限制

#include "stdio.h"

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 char string2[20];

 printf( "Enter a string : " );
 scanf( "%19[^\n]", string2 );

 printf( "\nstring2 is %19s\n", string2 );

 return 0;
}



上方程式碼中的第 9 行 scanf( "%19[^\n]", string2 );  我們用了  "%19[^\n]" 代替原本的 "%19s"

中括弧內代表的是掃描字符集合

我們在裡面使用了 ^ ,代表遇到字元 \n 後,結束 scanf 提取的意思

也就是說該語句會掃描使用者從鍵盤輸入的字元,並且不斷的掃描直到遇到 enter 鍵(即為\n) ,如此一來就可以避過 scanf 本身遇到空格會停止擷取的缺陷。

在上例中我們也測試了限制輸入字串長度為 19 是否起作用。由結果可以看出,我們輸入的字串大於 19 字元之後的字符(也就是ABC) 的確沒有被擷取以及列印顯示出來。

↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引