1. 程式人生 > >《C語言程式設計:現代方法(第2版)(K.N.King 著)》學習筆記九:格式化輸入/輸出(2)

《C語言程式設計:現代方法(第2版)(K.N.King 著)》學習筆記九:格式化輸入/輸出(2)

3.2 scanf 函式

  1. 就如同 printf 函式用特定的格式顯示輸出一樣,scanf 函式也根據特定的格式讀取輸入。像 printf 函式的格式串一樣,scanf 函式的格式串也可以包含普通字元轉換說明兩部分。scanf 函式轉換說明的用法和 printf 函式轉換說明的用法本質上是一樣的。
  2. 在許多情況下,scanf 函式的格式串只包含轉換說明。
  3. scanf 函式呼叫中像 "%d%d%f%f" 這樣“緊密壓縮”的格式串是很普遍的,而 printf 函式的格式串很少有這樣緊挨著的轉換說明。
  4. 像 printf 函式一樣,scanf 函式也有一些不易覺察的陷阱。使用 scanf 函式時,程式設計師必須檢查轉換說明的數量是否與輸入變數的數量相匹配,並且檢查每個轉換是否適合相對應的變數。與用 printf 函式一樣,編譯器無法檢查出可能的匹配不當。
  5. 另一個陷阱與符號“&”有關,通常把符號“&”放在 scanf 函式呼叫中每個變數的前面。符號“&”常常(但不總是)是需要的,記住使用它是程式設計師的責任。如果 scanf 函式呼叫中忘記在變數前面放置符號“&”,將會產生不可預知且可能是毀滅性的結果。程式崩潰是常見的結果。最起碼不會把從輸入讀進來的值儲存到變數中,變數將保留原有的值(如果沒有給變數賦初值,那麼這個原有值可能是沒有意義的。)忽略符號“&”是極為常見的錯誤,一定要小心!一些編譯器可以檢查出這種錯誤,併產生一條類似“format argument is not a pointer”的警告訊息。(術語“指標”將在今後的學習中定義,符號“&”用於建立一個指向變數的指標。)如果獲得警告訊息,檢查一下是否丟失了符號“&”。
  6. 呼叫 scanf 函式是讀資料的一種有效但不理想的方法。許多專業C程式設計師會避免用 scanf 函式,而是採用字元格式讀取所有資料,然後再把它們轉換成數值形式。
  7. 但是要注意,如果使用者錄入了非預期的輸入,那麼許多程式都將無法正常執行。在今後的學習中我們將會看到,可以用程式測試 scanf 函式是否成功讀入了要求的資料(若不成功,還可以試圖恢復)。

3.2.1 scanf 函式的工作方法

  1. scanf 函式本質上是一種“模式匹配”函式,試圖把輸入的字元組與轉換說明相匹配。
  2. 像 printf 函式一樣,scanf 函式是由格式串控制的。呼叫時,scanf 函式從左邊開始處理字串中的資訊。對於格式串中的每一個轉換說明,scanf 函式從輸入的資料中定位適當型別的項,並在必要時跳過空格。然後,scanf 函式讀入資料項,並且在遇到不可能屬於此項的字元時停止。如果讀入資料項成功,那麼 scanf 函式會繼續處理格式串的剩餘部分;如果某一項不能成功讀入,那麼 scanf 函式將不再檢視格式串的剩餘部分(或者餘下的輸入資料)而立即返回。
  3. 在尋找數的起始位置時,scanf 函式會忽略空白字元(white-space character,包括空格符、水平和垂直製表符、換頁符和換行符)。因此,我們可以把數字放在同一行或者分散在幾行內輸入,scanf 函式會把它們看成是一個連續的字元流。
  4. 在要求讀入整數時,scanf 函式首先尋找正號或負號,然後讀取數字直到讀到一個非數字時才停止。當要求讀入浮點數時,scanf 函式會尋找一個正號或負號(可選),隨後是一串數字(可能含有小數點),再後是一個指數(可選)。指數由字母“e”(或者字母“E”)、可選的符號和一個或多個數字構成。在用於 scanf 函式時,轉換說明 %e%f%g 是可以互換的,這 3 種轉換說明在識別浮點數方面都遵循相同的規則。
  5. 當 scanf 函式遇到一個不可能屬於當前項的字元時,它會把此字元“放回原處”,以便在掃描下一個輸入項或者下一次呼叫 scanf 函式時再次讀入。
  6. 思考下面(公認有問題的)4 個數的排列:

1-20.3-4.0e3(末尾還有一個換行符)

  • 我們使用這樣的 scanf 函式呼叫讀取上面的數:scanf("%d%d%f%f", &i, &j, &x, &y);
  • 這是 scanf 函式處理這組輸入的過程:(1)第一個轉換說明 %d —— 第一個非空的輸入字元是 1;因為整數可以以 1 開始,所以 scanf 函式接著讀取下一個字元,即“-”。scanf 函式識別出字元“-”不能出現在整數內,所以把 1 存入變數 i 中,而把字元“-”放回原處。(2)第二個轉換說明 %d —— 隨後,scanf 函式讀取字元“-”、“2”、“0”和“.”(句點)。因為整數不能包含小數點,所以 scanf 函式把 -20 存入變數 j 中,而把字元“.”放回原處。(3)第三個轉換說明 %f —— 接下來 scanf 函式讀取字元“.”、“3”和“-”。因為浮點數不能在數字後邊有負號,所以 scanf 函式把 0.3 存入變數 x 中,而將字元“-”放回原處。(4)第四個轉換說明 %f —— 最後,scanf 函式讀取字元“-”、“4”、“.”、“0”、“e”、“3”和換行符。因為浮點數不能包含換行符,所以 scanf 函式把 -4.0×10310^{3} 存入變數 y 中,而把換行符放回原處。
  • 在這個例子中,scanf 函式能夠把格式串中的每個轉換說明與一個輸入項進行匹配。因為換行符沒有讀取,它將留給下一次 scanf 函式呼叫。

3.2.2 格式串中的普通字元

  1. 處理格式串中的普通字元時,scanf 函式採取的動作依賴於這個字元是否為空白字元:
  • 空白字元:當在格式串中遇到一個或多個連續的空白字元時,scanf 函式從輸入中重複讀空白字元直到遇到一個非空白字元(把該字元“放回原處”)為止。格式串中空白字元的數量無關緊要,格式串中的一個空白字元可以與輸入中任意數量的空白字元相匹配。(附帶提一下,在格式串中包含空白字元並不意味著輸入中必須包含空白字元。格式串中的一個空白字元可以與輸入中任意數量的空白字元相匹配,包括零個。)
  • 其他字元:當在格式串中遇到非空白字元時,scanf 函式將把它與下一個輸入字元進行比較。如果兩個字元相匹配,那麼 scanf 函式會把不匹配的字元放回輸入中,然後異常退出,而不進一步處理格式串或者從輸入中讀取字元。
  1. 對於格式串 "%d/%d",如果我們的輸入是“5/96”,那麼 scanf 函式處理該輸入的過程如下:
  • scanf 函式讀取格式串中的第一個轉換說明 %d,然後從輸入中讀取“5”與之匹配。
  • scanf 函式讀取格式串中的 /,然後從輸入中讀取“/”與之匹配。
  • scanf 函式讀取格式串中的第二個轉換說明 %d,然後從輸入中讀取“96”與之匹配。
  1. 如果我們的輸入是“5(空格符)/(空格符)96”,那麼 scanf 函式處理該輸入的過程如下:
  • scanf 函式讀取格式串中的第一個轉換說明 %d,然後從輸入中讀取“5”與之匹配。
  • scanf 函式讀取格式串中的 /,並試圖把空格符與之匹配,但是二者不匹配,所以空格符被放回原處,把“(空格符)/(空格符)96” 留給下一次 scanf 函式呼叫來讀取。
  • scanf 函式異常退出。
  1. 為了允許第一個數後邊有空格,應使用格式串 %d(空格符)/%d

3.2.3 易混淆的 printf 函式和 scanf 函式

  1. 一個常見的錯誤是:在 printf 函式呼叫時在變數前面放置“&”。幸運的是,這種錯誤是很容易發現的:printf 函式將顯示一個樣子奇怪的數,而不是變數的值。
  2. 在尋找資料項時,scanf 函式通常會跳過空白字元。所以除了轉換說明,格式串常常不需要包含字元。假設 scanf 格式串應該類似於 printf 格式串是另一個常見錯誤,這種不正確的假定可能引發 scanf 函式行為異常。例如,我們這樣呼叫 scanf 函式:scanf("%d, %d", &i, &j);。倘若我們沒有在輸入時輸入“,”字元,那麼 scanf 函式就會在試圖匹配“,”字元時異常退出。這個 scanf 函式呼叫的最後結果是,變數 i 得到了輸入值,而變數 j 並沒有得到輸入值。
  3. printf 格式串常以 \n 結尾,但是千萬不要在 scanf 格式串末尾放置 \n。對 scanf 函式來說,格式串中的換行符等價於空格,兩者都會引發 scanf 函式提前進入到下一個非空白字元。例如,如果格式串是 "%d\n",那麼 scanf 函式在讀取一個整數後,會將換行符與輸入進行匹配。由於換行符等價於空格(格式串中的該字元可以與輸入中任意數量的該字元相匹配),無論我們按多少次回車鍵,輸入都無法終止。像這樣的格式串可能會導致互動式程式一直“掛起”直到使用者輸入一個非空白字元為止。

[程式] 分數相加:addfrac.c

/* Adds two fractions */

#include <stdio.h>
int main(void)
{
    int num1, denom1, num2, denom2, result_num, result_denom;

    printf("Enter first fraction: ");
    scanf("%d/%d", &num1, &denom1);

    printf("Enter second fraction: ");
    scanf("%d/%d", &num2, &denom2);

    result_num = num1 * denom2 + num2 * denom1;
    result_denom = denom1 * denom2;
    printf("The sum is %d/%d\n", result_num, result_denom);
    // 注意,結果並沒有化為最簡分數。

    return 0;
}