跳轉到內容

c 程式設計/stdio.h/scanf

來自華夏公益教科書,開放的書籍,為了一個開放的世界

scanf 是一個函式,它根據指定的格式從給定的字串流源讀取資料,源於C 程式語言,並且存在於許多其他程式語言中。

scanf 函式的原型是

int scanf(const char *format, ...);

該函式返回成功匹配的專案總數,該總數可能小於請求的總數。如果輸入流已耗盡或在任何專案匹配之前從該流讀取操作失敗,則返回 EOF。

就可追溯性而言,“scanf” 代表“掃描格式”,因為它掃描輸入以查詢有效標記並根據指定的格式解析它們。

scanf 函式存在於C 中,它從標準輸入(通常是命令列介面 或類似的文字使用者介面)讀取數字和其他資料型別 的輸入。

以下顯示了 C 程式碼,該程式碼從標準輸入讀取可變數量的無格式十進位制整數,並在單獨的行上打印出每個整數

#include <stdio.h>

int
main(void)
{
    int n;
    while (scanf("%d", & n))
        printf("%d\n", n);
    return 0;
}

在被上述程式處理後,如下的凌亂整數列表

456 123 789     456 12
456 1
      2378

將整齊地顯示為

456
123
789
456
12
456
1
2378

要打印出一個單詞

#include <stdio.h>

int
main(void)
{
    char word[20];
    if(scanf("%19s", word) == 1)
        puts(word);
    return 0;
}

無論程式設計師想要程式讀取什麼資料型別,引數(如上面的 &n)都必須是指向記憶體的指標。否則,該函式將無法正常執行,因為它將嘗試覆蓋錯誤的記憶體部分,而不是指向您嘗試獲取輸入的變數的記憶體位置。

由於 scanf 被指定為僅從標準輸入讀取,因此許多具有介面 的程式語言(如PHP)具有 sscanffscanf 等派生函式,但沒有 scanf 本身。

派生函式

[編輯 | 編輯原始碼]

根據實際的輸入源,程式設計師可以使用 scanf 的不同派生函式。兩個常見的例子是 sscanffscanf

fscanf 派生函式從指定的 檔案流 讀取輸入。原型如下

(CC++)

int fscanf (FILE *file, const char *format, ...);

(PHP)

mixed fscanf (resource file, const string format [, mixed args...])

fscanf 派生函式的工作方式與原始 scanf 函式類似 - 讀取的部分輸入在檔案關閉並重新開啟之前不會被再次讀取。

sscanf 派生函式從作為第一個引數傳遞的 字串 讀取輸入。與 fscanf 的一個重要區別是,每次呼叫該函式時,都會從字串的開頭讀取字串。沒有在成功讀取操作時遞增的“指標”。原型如下

(CC++)

int sscanf (const char *str, const char *format, ...);

(PHP)

mixed sscanf (const string str, const string format [, mixed args...])

vscanf、vsscanf 和 vfscanf

[編輯 | 編輯原始碼]
int vscanf (const char *format, va_list args);
int vsscanf (const char *source, const char *format, va_list args);
int vfscanf (FILE *file, const char *format, va_list args);

這些函式與沒有 v 字首的相同函式類似,只是它們從 va_list 中獲取引數。(參見 stdarg.h。)這些變體可用於可變引數函式。

格式字串規範

[編輯 | 編輯原始碼]

scanf 中的格式化佔位符 與其反函式 printf 中的格式化佔位符 大致相同。

格式字串中很少有常量(即不是格式化佔位符 的字元),主要是因為程式通常不是為了讀取已知資料而設計的。例外情況是 1 個或多個空白 字元,它們會丟棄輸入中的所有空白字元。

以下是一些最常用的佔位符

  • %d : 以帶符號十進位制 數字的形式掃描一個整數。
  • %i : 以帶符號數字的形式掃描一個整數。類似於 %d,但在前面有 0x 時將數字解釋為十六進位制,在前面有 0 時將數字解釋為八進位制。例如,字串 031 將使用 %d 讀取為 31,使用 %i 讀取為 25。%hi 中的標誌 h 表示轉換為 shorthh 表示轉換為 char
  • %u : 掃描十進位制 unsigned int(注意,在 C99 標準中,輸入值的負號是可選的,因此如果讀取到負數,則不會出現錯誤,結果將是二進位制補碼。參見 strtoul()Template:Failed verification) 類似地,%hu 掃描 unsigned short%hhu 掃描 unsigned char
  • %f : 以標準(定點)表示法掃描一個浮點數
  • %g%G : 以標準或指數表示法掃描一個浮點數。%g 使用小寫字母,%G 使用大寫字母。
  • %x%X : 以無符號十六進位制 數字的形式掃描一個整數。
  • %o : 以八進位制 數字的形式掃描一個整數。
  • %s : 掃描一個字串。掃描在空白 處結束。一個空字元 會儲存在字串的末尾,這意味著提供的緩衝區必須比指定的輸入長度至少長 1 個字元。
  • %c : 掃描一個字元 (char)。不會新增空字元
  • (空格): 空格掃描空白 字元。
  • %lf : 作為雙精度 浮點數掃描。
  • %Lf : 作為長雙精度 浮點數掃描。

以上可以在數值修飾符和 lL 修飾符(代表“長”)之間複合使用,這些修飾符位於百分號和字母之間。百分號和字母之間也可能存在數值(在 long 修飾符之前,如果有的話),指定要掃描的字元數。百分號之後的一個可選的星號 (*) 表示透過此格式說明符讀取的資料不應儲存在變數中。格式字串後面的引數不應包含此丟棄的變數。

printf 中的 ff 修飾符在 scanf 中不存在,導致輸入和輸出模式之間存在差異。llhh 修飾符在 C90 標準中不存在,但在 C99 標準中存在。[1]

格式字串的示例為

"%7d%s %c%lf"

以上格式字串將前七個字元掃描為十進位制整數,然後讀取剩餘字元作為字串,直到遇到空格、換行符或製表符,然後掃描其後的第一個非空白字元,以及其後的雙精度浮點數。

錯誤處理

[編輯 | 編輯原始碼]

scanf 通常用於程式無法保證輸入格式正確的情況。因此,健壯的程式必須檢查 scanf 呼叫是否成功,並採取相應的措施。如果輸入格式不正確,錯誤資料仍然會保留在輸入流中,必須在讀取新輸入之前讀取並丟棄。避免這種情況的另一種讀取輸入的方法是使用 fgets,然後檢查讀取的字串。最後一步可以使用 sscanf 完成,例如。

printf 一樣,scanf 容易受到 格式字串攻擊 的影響。應非常小心地確保格式化字串包含字串和陣列大小的限制。在大多數情況下,來自使用者的輸入字串大小是任意的;它無法在 scanf 函式執行之前確定。這意味著使用沒有長度說明符的 %s 佔位符本質上是不安全的,並且可以利用緩衝區溢位。另一個潛在問題是允許動態格式化字串,例如儲存在配置檔案或其他使用者控制檔案中的格式化字串。在這種情況下,除非事先檢查格式化字串並強制執行限制,否則無法指定允許的字串大小的輸入長度。與此相關的是額外的或不匹配的格式佔位符,它們與實際的 vararg 列表不匹配。這些佔位符可能會部分從堆疊中提取,包含不希望的甚至不安全的指標,具體取決於 varargs 的特定實現。

/* 另一種僅在某些特殊編譯器上有效的用法是

scanf("請輸入一個值 %d",&n);

它會列印引號內的字串,並在指示的 % 號處停止接受輸入。*/

[編輯 | 編輯原始碼]
  1. C99 標準,第 7.19.6.2 節“fscanf 函式” 第 11 行。
華夏公益教科書