C語言中文網 目錄

C語言格式化輸入

當從一個格式化數據源中讀取數據時,C 語言提供了 scanf()函數系列。與 printf()函數一樣,scanf()函數需要一個格式化字符串作為其參數,以控制 I/O 格式與程序內部數據之間的轉換。本文介紹在 scanf()和 printf()函數中使用格式化字符串和轉換修飾符的差異。

scanf()函數系列

各種 scanf()函數處理輸入源字符的方式都是相同的。不同的是這些函數所針對的數據源種類,以及它們接收參數的方式。下面的 scanf()函數針對字節導向流:
int scanf(const char*restrict format,...);
從標準輸入流 stdin 中讀取數據。
int fscanf(FILE*restrict fp,const char*restrict format,...);
從 fp 所引用的輸入流中讀取數據。
int sscanf(const char*restrict src,const char*restrict format,...);
從 src 指向的 char 數組中讀取數據。

省略號表示還有更多的可選參數。可選參數是指向變量的指針,scanf()函數將轉換結果存儲在這些變量中。

類似于 printf()函數,scanf()函數系列也包含變體版本。變體版本將一個指針作為參數,指向一個參數列表,而不是在函數調用時直接接收數量可變的參數。

這些變體版本的函數名稱以字母 v 開頭,表示“variable argument list”(可變參數列表):例如,vscanf()、vfscanf()和 vsscanf()。如果想使用支持可變參數列表的函數,除了頭文件 stdio.h 以外,還必須包含頭文件 stdarg.h。

這些函數都具有相應的針對寬字符導向流的版本。寬字符函數名稱中包含 wscanf 而不是 scanf,例如 wscanf()和 vfwscanf()。

C11 標準為這些函數都提供了一個新的“安全”的版本。這些對應的新函數均以后綴 _s(如 fscanf_s())。新函數測試在讀入一個字符串到數組之前,是否超出了數組邊界。

格式化字符串

scanf()函數的格式化字符串包含普通字符和轉換說明,轉換說明定義了如何解釋以及轉換讀入的字符序列。scanf()函數所使用的大多數轉換修飾符都與 printf()函數所定義的一樣。然而,scanf()函數的轉換說明沒有標記和精度選項。針對 scanf()函數轉換說明的通用語法如下所示:

%[*][字段寬度][長度修飾符]修飾符


對于格式化字符串中的每個轉換說明,從輸入源讀入的字符的數量與轉換方式都會與轉換修飾符一致。結果會存儲在對應指針參數所指向的對象中。如下例所示:
int age = 0;
char name[64] = "";
printf( "Please enter your first name and your age:\n" );
scanf( "%s%d", name, &age );

假設用戶在提示符下輸入如下內容:
Bob 27\n

調用 scanf()函數,會將字符串 Bob 寫進 char 數組 name 中,然后將 27 寫進 int 變量 age 中。

所有的轉換說明,除了具有修飾符 c 的情況以外,都會忽略前面的空白字符(whitespace character)。在上例中,用戶可以在第一個詞 Bob 前,或者在 Bob 與 27 之間,放置任意多個空格、制表符或換行符,這些操作均不影響結果。

針對給定的轉換說明,當 scanf()讀到任何空白字符時,或者任何無法以該轉換說明解釋的字符時,讀取序列字符的操作將會終止。無法被解釋的字符會被放回到輸入流中,下一個轉換說明將從該字符開始。在前述例子中,假設用戶輸入如下:
Bob 27years\n

在讀取到字符 y 時,它不可能是十進制數值的一部分,針對轉換說明 %d,scanf()會停止讀取對應的字符。在調用該函數后,字符 years\n 會繼續留在輸入流的緩沖區中。

如果在忽略所有空白符之后,scanf()還是找不到符合當前轉換說明的字符,則生成錯誤,scanf()函數終止處理輸入。下面將介紹如何捕獲這類錯誤。

通常,在調用 scanf()函數時,格式化字符串只包含轉換說明。如果不是,那么格式化字符串中除轉換說明與空白符以外的其他所有字符,必須與輸入源對應位置的字符完全一致。否則 scanf()函數就會終止處理,并將不匹配的字符放回到輸入流中。

格式化字符串中所出現的一個或多個連續空白符,必須符合輸入流中連續空格的數量。換句話說,對于格式化字符串中出現的所有空白符,scanf()會讀取并略過數據源中的所有空白字符,直到讀入第一個非空白符。在理解這一點后,請判斷下面的 scanf()調用方式有什么問題。
scanf( "%s%d\n", name, &age );    // 有什么問題

假設用戶輸入下面這一行字符:
Bob 27\n

本例中,scanf()在讀入換行符后不會返回,而是繼續讀取更多輸入,直到出現非空白字符出現。

有時候,需要讀取并略過符合給定轉換說明的字符序列,不存儲結果。可以在轉換說明中采用 %* 來達到前述效果。對于具有星號的轉換說明,不要包括對應的指針參數。

scanf()函數的返回值是成功存儲數據項的數量。如果一切執行順利,返回值就是轉換說明的數量(但不計包含星號的轉換說明)。如果發生讀取錯誤或在轉換數據項前就到達了輸入源尾部,則 scanf()函數會返回值 EOF。如下例所示:
if ( scanf( "%s%d", name, &age ) < 2 )
  fprintf( stderr, "Bad input.\n" );
else
{ /* ...測試存儲的值... */ }

字段寬度

字段寬度是十進制整型正數,它指定了對于給定的轉換說明,scanf()所讀取字符的最大數量。對于字符串輸入來說,字段寬度可以防止緩沖區出現溢出情況:
char city[32];
printf( "Your city: ");
if ( scanf( "%31s", city ) < 1 )     // 不要讀入超過31個字符
  fprintf( stderr, "Error reading from standard input.\ n" );
else
/* ... */

printf()會輸出超過指定字段寬度的字符,但 scanf()不同于 printf(),轉換修飾符 s 不會讀入超過指定字段寬度的字符到緩沖區。

讀取字符和字符串

轉換說明 %c 和 %1c 都會從輸入流中讀取一個字符,包括空白符。通過指定字段寬度,可以讀取數量等于字段寬度的字符,包括空白符,只要沒有遇到輸入流的結束。當采用這種方式讀取多個字符時,對應的指針參數必須指向一個空間足夠大的 char 數組,以存儲下所有讀到的字符。

使用轉換修飾符 c 的 scanf()函數,不會在讀入字符序列的尾部加上字符串終止符。例如:
scanf( "%*5c" );
該 scanf()調用會讀取并丟棄輸入源緊接著的 5 個字符。

轉換說明 %s 總是讀取恰好一個詞,遇到空白符時結束讀取。如果想讀取整行文本,可以使用函數 fgets()。

下面的示例逐詞地讀取文本文件的內容。假設文件指針 fp 關聯了一個文本文件,并且該文件已打開,以用于讀取:
char word[128];
while ( fscanf( fp, "%127s", word ) == 1 )
{
   /* ...處理讀到的詞... */
}

除了轉換修飾符 s 以外,也可以使用“掃描集”(scanset)修飾符來讀取字符串,它由方括號所包含的一串無序字符組成([scanset])。scanf()函數接著讀取所有字符,然后將它們存儲為一個字符串(帶有字符串終止符),直到遇到不匹配掃描集中任一字符時才停止。例如:
char strNumber[32];
scanf( "%[0123456789]", strNumber );

如果用戶輸入 345X67,那么 scanf()會把 345\0 字符串存儲到數組 strNumber 中。字符 X 以及后續字符則仍然留在輸入緩沖區中。

逆向使用轉換掃描集(也就是說,除掃描集中的字符外,其他都符合),做法是在掃描集的左括號后面加上一個插入號(^)。下面的 scanf()調用讀取所有字符(包括空白符),直到句子結束的標點符號,然后再讀入標點符號本身:
char ch, sentence[512];
scanf( "%511[^.!?]%c", sentence, &ch );

下面的 scanf()調用讀取并丟棄所有字符,一直到當前行結束:
scanf( "%*[^\n]%*c" );

讀取整數

類似 printf()函數,scanf()函數為整數提供了下面的轉換修飾符:d、i、u、o、x 和 X。它們允許讀入十進制、八進位與十六進制表示法,并轉換為 int 或 unsigned int 變量。如下例所示:
// 讀入一個非負的十進制整數
unsigned int value = 0;
if ( scanf( "%u", &value ) < 1 )
  fprintf( stderr, "Unable to read an integer.\n" );
else
  /* ... */

對于 scanf()函數內的修飾符 i,讀入數字的基數(進制)并非預先定義好的。基數是由讀入的數字字符序列的前綴符號所決定的,這些符號的表示方式與 C 源代碼中整數常量相同。

如果字符序列不是以 0 開始,那么它會被解釋為十進制數字。如果以 0 開始,并且第二個字符不是 x 或 X,那么該序列會被解釋為八進位數字。如果以 0x 或 0X 開始,則以十六進制數字讀入。

如果想把所讀取的整數賦值給一個 short、char、long 或 long long 變量(或者它們所對應的無符號類型),必須在轉換修飾符之前插入一個長度修飾符:h 表示 short,hh 表示 char,l 表示 long,ll 表示 long long。在下面的示例中,FILE 指針 fp 指向一個打開用于讀取的文件:
unsigned long position = 0;
if (fscanf( fp, "%lX", &position) < 1 )  // 讀取一個十六進制整數
  /* ... 處理錯誤:無法讀入數字... */

讀取浮點數

當處理浮點數時,scanf()函數使用與 printf()相同的轉換修飾符:f、e、E、g 和 G。而且,C99 新增了修飾符 a 和 A。所有這些修飾符以同樣的方式解釋讀取的字符序列。可以被解釋成浮點數的字符序列,與 C 語言中的有效浮點常量是一樣的。scanf()也可以轉換整數,并將它們存儲在浮點變量中。

所有這些修飾符將數字轉換成 float 類型浮點值。如果想將它們轉換并存儲成 double 或 long double,必須插入一個長度修飾符:double 使用 l(小寫L),long double 則使用 L。如下例所示:
float x = 0.0F;
double xx = 0.0;
// 讀取兩個浮點數:將一個轉換為float,另一個轉換為double
if ( scanf( "%f %lf", &x, &xx ) < 2 )
  /* ... */

如果該 scanf()調用接收到的輸入序列是 12.37\n,那么會將 12.3 存儲在到 float 變量 x 中,而 7.0 存儲到 double 變量 xx 中。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo