跳轉到內容

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 中的格式佔位符大致相同。

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

以下是最常用的佔位符

  • %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 : 掃描一個字元字串。掃描在遇到空白字元時結束。一個空字元儲存在字串的末尾,這意味著提供的緩衝區必須比指定的輸入長度至少長一個字元。
  • %c : 掃描一個字元(char)。不新增任何空字元
  • (space): 空格掃描空白字元
  • %lf : 掃描為雙精度浮點數。
  • %Lf : 掃描為長雙精度浮點數。

以上可以在數字修飾符和表示“long”的lL修飾符之間進行組合使用。在百分號和字母之間,還可以有數值(在任何“long”修飾符之前),指定要掃描的字元數。百分號後面的可選星號 (*) 表示透過此格式說明符讀取的資料不儲存在變數中。格式字串後面的引數不應包含此丟棄的變數。

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

格式字串的示例為

"%7d%s %c%lf"

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

錯誤處理

[edit | edit source]

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

安全

[edit | edit source]

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

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

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

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

另請參見

[edit | edit source]
[edit | edit source]
  1. C99 標準,§7.19.6.2 “fscanf 函式” 第 11 行。
華夏公益教科書