# *2.5 C 語言的 scanf( ) 格式化輸入函數

我們已經學會如何用 printf 讓程式輸出精美的訊息。但一個真正有用的程式，不僅要會「說」，更要會「聽」。它需要接收使用者的指令、數據，才能進行下一步的處理。

scanf (scan formatted) 就是這座溝通的橋樑。它會暫停程式的執行，靜靜地等待使用者從鍵盤輸入資料，然後依照我們指定的「格式」去解析這些輸入，並將它們存放到對應的變數中。

同樣地，要使用 scanf，請務必在程式開頭引用標頭檔：

```cpp
#include <cstdio>
```

## 2.5.1- scanf 的核心語法與「&」的秘密

scanf 的語法看起來和 printf 有點像，但有一個關鍵且絕對不能忘記的區別。

```C++
scanf("格式化字串", &變數1, &變數2, ...);
```

* **格式化字串：** 由一或多個「格式指定符」組成，用來告訴 scanf 使用者將會輸入什麼類型的資料。例如，"%d" 代表使用者會輸入一個整數。
* **& (取址運算子)：** 這是 scanf 最重要、也最容易出錯的地方！

讓我們用一個生活化的比喻來理解：

想像一下，變數是一個「置物櫃」，裡面可以存放資料。

* 當你使用 `printf("%d", score);` 時，你是告訴 printf：「打開 score 這個櫃子，把裡面的東西（值）拿出來秀給大家看。」
* 當你使用 `scanf("%d", &score);` 時，你是告訴 scanf：「我給你 score 這個櫃子的地址（&score），請你把使用者輸入的東西，親自送到這個地址的櫃子裡放好。」


scanf 需要的是「存放資料的地址」，而不是「變數裡現有的值」。所以，除了字串陣列（我們稍後會提）以外，`幾乎所有變數在使用 scanf 時都必須在前面加上 & 符號！`

## 2.5.2- scanf 的各種應用與常見陷阱

常用的格式指定符：

|指定符|對應資料類型|說明|
|-----|----------|---|
|%d|int|讀取一個十進位整數。|
|%f|float|讀取一個浮點數。|
|%lf|double|注意！ 讀取 double 類型時要用 %lf (long float)，這是新手常犯的錯誤。|
|%c|char|讀取一個單一字元。|
|%s|字串 (char 陣列)|讀取一個字串（但遇到空白、Tab或換行時會停止）。|

**範例 1：讀取學生的基本資料**

```C++
#include <cstdio>

int main() {
    int age;
    double height;

    printf("請輸入你的年齡：");
    scanf("%d", &age);

    printf("請輸入你的身高 (公尺)：");
    scanf("%lf", &height); // 讀取 double，使用 %lf

    printf("好的，你 %d 歲，身高 %.2f 公尺。\n", age, height);
    return 0;
}
```
    
**執行過程：**

<div class="coutput">
請輸入你的年齡：17 (使用者輸入後按 Enter)
請輸入你的身高 (公尺)：1.75 (使用者輸入後按 Enter)
好的，你 17 歲，身高 1.75 公尺。
</div>

你可以在格式化字串中放置多個指定符，scanf 會要求使用者一次輸入多個值，並用空白、Tab 或換行鍵來分隔它們。

### 範例 2：輸入座標

```C++
#include <cstdio>

int main() {
    int x, y;
    printf("請輸入一個二維座標 (例如: 15 30)：");
    scanf("%d %d", &x, &y);
    printf("你輸入的座標點為 (%d, %d)\n", x, y);
    return 0;
}
```

**執行過程：**

```
請輸入一個二維座標 (例如: 15 30)：15 30
你輸入的座標點為 (15, 30)
```

這是 scanf 最經典的陷阱！當你讀取完一個數字後，緊接著要讀取一個字元時，常常會出問題。

**範例 3 (錯誤示範)：**

```C++
#include <cstdio>

int main() {
    int choice;
    char confirm;
    printf("請選擇項目 (1-3): ");
    scanf("%d", &choice);
    printf("你確定嗎? (Y/N): ");
    scanf("%c", &confirm); // 這行會出問題
    printf("你的選擇是 %d, 確認字元是 '%c'\n", choice, confirm);
    return 0;
}
```

**執行過程：**
<div class="coutput">
請選擇項目 (1-3): 2 (使用者輸入 2 後按 Enter)
你確定嗎? (Y/N): 你的選擇是 2, 確認字元是 '
'
</div>

**問題分析：**

當你輸入 2 並按下 Enter 鍵時，你其實輸入了兩個字元：`'2'` 和 `'\n'` (換行符號)。

`scanf("%d", ...)` 只讀走了 `'2'`，那個 `'\n'` 還留在輸入緩衝區中。輪到 `scanf("%c", ...)` 時，它立刻把還留著的 `'\n'` 讀走了，導致程式根本不等待你輸入 Y 或 N。

**解決方案：**

在 `%c` 前面加一個空格，`" %c"`。這個空格會告訴 scanf：「請忽略前面所有空白類的字元（包含空格、Tab、換行符），然後再讀取下一個真正的字元。」

**範例 3 (正確寫法)：**

```C++
#include <cstdio>
int main() {
    int choice;
    char confirm;
    printf("請選擇項目 (1-3): ");
    scanf("%d", &choice);
    printf("你確定嗎? (Y/N): ");
    scanf(" %c", &confirm); // 在 %c 前加一個空格
    printf("你的選擇是 %d, 確認字元是 '%c'\n", choice, confirm);
    return 0;
}
```

%s 雖然方便，但`它遇到任何空白字元（空格、Tab、換行）就會停止讀取`。

**範例 4：讀取姓名**

```C++
#include <cstdio>

int main() {
    char name[30];
    printf("請輸入你的英文全名 (例如: Peter Pan): ");
    scanf("%s", name); // 注意：字串陣列 name 本身就是位址，所以不用加 &
    printf("你好, %s！\n", name);
    return 0;
}
```

**執行過程：**

<div class="coutput">
請輸入你的英文全名 (例如: Peter Pan): Peter Pan
你好, Peter！
</div>

**問題分析：**

scanf 只讀到了 `"Peter"` 就因為遇到空格而停止了，`"Pan"` 則被留在了後面。

這也是為什麼在 C++ 中，當需要讀取一整行含有空格的文字時，我們未來會學習更適合的 cin.getline() 或 fgets() 函式。

## 2.5.3- 總結與速查表

scanf 是程式接收輸入的基礎，雖然有些小陷阱，但只要小心使用，它依然非常強大。

|指定符|對應資料類型|關鍵提醒|
|-----|----------|------|
|%d|int|變數前記得加 &。例如 `scanf("%d", &num);`|
|%f|float|變數前記得加 &。例如 `scanf("%f", &price);`|
|%lf|double|務必使用 %lf，而非 %f。變數前記得加 &。例如 `scanf("%lf", &pi);`|
|%c|char|變數前記得加 &。若前面有其他輸入，建議用 `" %c"` 來清除換行符。|
|%s|char[]|變數前不用加 &。只能讀取不含空白的字串。|

## 2.5.4- 練習題作業

###### 練習題 1：華氏溫度轉換

* **目標：** 練習讀取浮點數 (double) 並進行計算。
* **說明：** 請使用者輸入攝氏溫度，程式將其轉換為華氏溫度後輸出。
* **公式：** 華氏 = 攝氏 * 9 / 5 + 32
* 執行範例：
```
請輸入攝氏溫度：25.0
轉換後的華氏溫度為：77.0
```

---

###### 練習題 2：簡易對話程式

* **目標：** 綜合練習 %d, %s。
* **說明：** 撰寫一個程式，詢問使用者的名字和學號，然後向他打招呼。
* **執行範例：**

<div class="coutput">
你好！請問你叫什麼名字？ (請輸入英文名)
David
你的學號是幾號？
10123
你好, David (學號: 10123)，歡迎使用本系統！
</div>

---

###### 練習題 3：解決字元輸入問題

* **目標：** 練習處理 %c 的換行符陷阱。
* **說明：** 撰寫一個程式，讓使用者輸入一個整數代表分數，然後再輸入一個字元代表評等 (A/B/C/D)。最後將兩者一起輸出。請務必確保程式能正確等待使用者輸入評等。
* **執行範例：**

<div class="coutput">
請輸入你的分數：88
請輸入你的評等 (A-F)：A
你的成績是 88 分，評等為 A。
</div>

---

<link rel=stylesheet type="text/css" href="https://nlbook.ddns.net/css/h.css">