C語言標準輸入完整教學:scanf 與 fgets 的安全用法與範例

目次

1. 前言

在學習C語言的過程中,處理使用者輸入的「標準輸入」是不可避免的重要功能。正確理解並安全地使用標準輸入,可以大幅提升程式的通用性與可靠性。

本文將從基礎到進階,系統性地解說C語言中的標準輸入。為了讓初學者也能輕鬆理解,內容會搭配範例程式碼,請務必將其應用到您自己的程式開發中。

標準輸入的重要性

標準輸入是程式從外部接收資料的基本機制。例如,常見的使用場景包括:

  • 使用者輸入數字進行計算的應用程式
  • 透過字串進行搜尋的功能
  • 根據命令列指令動態執行的程式

在這些情況下,正確處理標準輸入能夠建立高效又安全的程式。

本文將學到的內容

本文將涵蓋以下主題:

  1. 標準輸入的基本原理與在C語言中的實作方法
  2. scanffgets 等函式的使用方式
  3. 安全且可靠的標準輸入實作方式
  4. 進階的資料處理技巧
  5. 常見問題與解決方法

適合的讀者

  • 第一次學習C語言的初學者
  • 對標準輸入用法沒有信心的程式設計師
  • 想實現安全且高效輸入處理的人

2. 什麼是標準輸入

在C語言中,標準輸入是程式接收外部資料的基本機制。它屬於「標準輸入輸出」的一部分,通常透過終端機或命令列由使用者提供資料。本節將解釋標準輸入的基本概念與角色。

標準輸入的定義

標準輸入(Standard Input,stdin)指的是程式用來接收外部資料的資料流。在C語言中,可以透過 stdio.h 標頭檔輕鬆操作標準輸入。

  • 資料通常由鍵盤輸入。
  • 輸入的資料會在程式中處理,並輸出到標準輸出。

標準輸入輸出的機制

C語言內建以下三種標準資料流:

  1. 標準輸入(stdin):用來接收外部資料。
  2. 標準輸出(stdout):用來輸出程式結果。
  3. 標準錯誤輸出(stderr):用來輸出錯誤訊息。

實際範例

以下範例示範如何使用標準輸入讀取數字,並將結果輸出到標準輸出。

#include <stdio.h>

int main() {
    int number;
    printf("請輸入一個數字: ");
    scanf("%d", &number); // 從標準輸入讀取整數
    printf("您輸入的數字是: %d\n", number); // 輸出到標準輸出
    return 0;
}
侍エンジニア塾

3. C語言中的標準輸入基礎

在C語言中,為了從使用者取得輸入,有多種函式可用。其中最基本的是 scanffgets。本節將透過具體範例,說明這些函式的使用方式與特性。

scanf 函式的使用方法

scanf 是透過格式指定子解析輸入資料並存入變數的函式。

基本語法

int scanf(const char *format, ...);
  • format:用來指定資料格式的字串。
  • ...:儲存輸入資料的變數位址。

常用的格式指定子

指定子說明範例
%d整數42
%f浮點數3.14
%c單一字元A
%s字串Hello

使用範例

#include <stdio.h>

int main() {
    int age;
    printf("請輸入年齡: ");
    scanf("%d", &age); // 讀取整數
    printf("您輸入的年齡是: %d\n", age);
    return 0;
}

注意事項

  • 若輸入格式不正確,可能會導致錯誤。
  • 為避免緩衝區溢位,務必限制輸入大小(例如使用 %s 時)。

fgets 函式的使用方法

fgets 用於逐行讀取字串,相較於 scanf 更安全。

基本語法

char *fgets(char *str, int n, FILE *stream);
  • str:用來存放輸入的字元陣列。
  • n:最多可讀取的字元數(陣列大小)。
  • stream:輸入來源(通常為 stdin)。

使用範例

#include <stdio.h>

int main() {
    char name[50];
    printf("請輸入姓名: ");
    fgets(name, 50, stdin); // 最多讀取49個字元
    printf("您輸入的姓名是: %s", name);
    return 0;
}

注意事項

  • 若輸入超過緩衝區大小,超出的資料會被截斷。
  • 若包含換行符號,需手動刪除。

刪除換行字元的方法

name[strcspn(name, "\n")] = '\0';

scanffgets 的比較

特性scanffgets
用途依格式讀取數字或字串逐行讀取字串
安全性可能有緩衝區溢位風險可限制讀取大小
靈活性可使用格式指定可完整取得輸入字串

該使用哪一個?

  • 使用 scanf 的情況:需要直接讀取數字或特定格式資料。
  • 使用 fgets 的情況:重視安全性,或需要處理長字串、複雜輸入。

4. 安全的標準輸入實作

在C語言中處理標準輸入時,確保安全性非常重要。不當的輸入處理可能導致緩衝區溢位或未定義行為,進而降低程式的可靠性。本節將透過實例,解說如何安全地實作標準輸入。

防止緩衝區溢位

scanf 雖然方便,但若未限制輸入大小,可能發生緩衝區溢位。解決方法是使用格式指定子限制輸入長度。

限制輸入大小的範例

#include <stdio.h>

int main() {
    char input[10];
    printf("請輸入最多9個字元: ");
    scanf("%9s", input); // 最多讀取9個字元
    printf("您輸入的字串是: %s\n", input);
    return 0;
}

重點

  • 使用 %9s 等方式限制輸入大小,避免超過陣列空間。

使用 fgets 來提升安全性

fgets 可指定讀取字數,因此能降低緩衝區溢位風險,特別適合處理長字串或逐行輸入。

fgets 實作範例

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[20];
    printf("請輸入字串: ");
    fgets(buffer, sizeof(buffer), stdin); // 最多讀取19個字元
    buffer[strcspn(buffer, "\n")] = '\0'; // 移除換行
    printf("您輸入的字串是: %s\n", buffer);
    return 0;
}

重點

  • 使用 sizeof(buffer) 自動設定緩衝區大小。
  • 透過 strcspn 移除換行字元。

驗證輸入資料

為確保安全,必須驗證使用者輸入。例如,當需要整數時,先以字串接收再轉換,並檢查是否正確。

數字輸入驗證範例

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    char buffer[20];
    long number;
    char *endptr;

    printf("請輸入整數: ");
    fgets(buffer, sizeof(buffer), stdin);

    errno = 0; // 重設錯誤狀態
    number = strtol(buffer, &endptr, 10); // 轉換成整數

    if (errno != 0 || endptr == buffer || *endptr != '\0') {
        printf("請輸入有效的整數。\n");
    } else {
        printf("您輸入的整數是: %ld\n", number);
    }

    return 0;
}

重點

  • strtol 可檢測轉換時的錯誤。
  • 搭配 errnoendptr 驗證輸入是否正確。

錯誤處理的最佳實踐

在標準輸入中實作錯誤處理,能有效提升程式可靠性。

錯誤檢查範例

#include <stdio.h>

int main() {
    int value;
    printf("請輸入整數: ");

    if (scanf("%d", &value) != 1) { // 若輸入失敗
        printf("輸入無效。\n");
        return 1; // 錯誤結束
    }

    printf("您輸入的整數是: %d\n", value);
    return 0;
}

重點

  • 檢查 scanf 的回傳值,若輸入格式錯誤,執行錯誤處理。

安全輸入處理總結

  • 使用 scanf 時,必須限制輸入大小。
  • 處理長字串或逐行輸入時,建議使用 fgets
  • 不要完全信任使用者輸入,務必進行驗證與錯誤處理。

5. 標準輸入的進階處理

在掌握標準輸入的基本用法後,進一步學習進階處理方法,可以讓程式應對更複雜的輸入資料與情境。本章將解說如何高效處理多筆輸入、換行與空白字元,以及動態資料。

一次輸入多筆資料

標準輸入常需要一次讀取多筆資料,以下將介紹以空白或逗號分隔資料的處理方式。

處理以空白分隔的輸入範例

#include <stdio.h>

int main() {
    int numbers[5];
    printf("請輸入5個整數(以空白分隔): ");
    for (int i = 0; i < 5; i++) {
        scanf("%d", &numbers[i]); // 以空白分隔輸入
    }

    printf("您輸入的整數為: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

處理以逗號分隔的輸入範例

#include <stdio.h>

int main() {
    int numbers[3];
    printf("請輸入3個整數(以逗號分隔): ");
    scanf("%d,%d,%d", &numbers[0], &numbers[1], &numbers[2]); // 以逗號分隔輸入

    printf("您輸入的整數為: %d, %d, %d\n", numbers[0], numbers[1], numbers[2]);
    return 0;
}

處理包含換行與空白的輸入

標準輸入有時會混雜換行符與空白符,以下示範正確處理方式。

處理換行字元範例

可利用 fgets 讀取,再移除換行字元。

#include <stdio.h>
#include <string.h>

int main() {
    char input[100];
    printf("請輸入一段文字: ");
    fgets(input, sizeof(input), stdin);

    input[strcspn(input, "\n")] = '\0'; // 移除換行
    printf("您輸入的文字為: %s\n", input);

    return 0;
}

處理多行輸入

多行輸入在文字分析或資料處理中十分常見。

逐行處理輸入範例

#include <stdio.h>

int main() {
    char line[100];

    printf("請輸入多行文字(結束請按 Ctrl+D):\n");
    while (fgets(line, sizeof(line), stdin) != NULL) {
        printf("讀取到的行: %s", line);
    }

    return 0;
}

處理動態輸入資料

若輸入筆數不固定,需要彈性處理。以下範例示範計算多筆整數的總和。

動態資料處理範例

#include <stdio.h>

int main() {
    int number, sum = 0;
    printf("請輸入整數(結束請按 Ctrl+D):\n");

    while (scanf("%d", &number) == 1) {
        sum += number;
    }

    printf("輸入整數的總和為: %d\n", sum);
    return 0;
}

小結

透過進階的標準輸入技巧,可以更靈活地處理多筆資料、換行與動態輸入。熟悉這些技巧後,能設計出更強大的程式。

6. 常見問題與解決方法

在C語言中處理標準輸入時,初學者經常會遇到一些問題。本章整理了常見的錯誤與解決方法,幫助您建立更可靠的程式。

scanf 的問題與解決方法

問題1: 輸入跳過問題

使用 scanf 讀取字串或整數時,換行或空白字元可能會影響下一次輸入。

範例
#include <stdio.h>

int main() {
    int number;
    char letter;

    printf("請輸入整數: ");
    scanf("%d", &number);

    printf("請輸入字元: ");
    scanf("%c", &letter); // 會讀取到殘留的換行字元

    printf("輸入的整數: %d, 字元: %c\n", number, letter);
    return 0;
}
解決方法

scanf 格式前加入空白字元,忽略多餘的換行。

scanf(" %c", &letter); // 空白可忽略多餘換行

問題2: 緩衝區溢位

當使用 scanf 讀取字串時,若輸入超過陣列大小,可能造成程式崩潰。

範例
char input[10];
scanf("%s", input); // 若輸入超過10字元,會溢位
解決方法

在格式指定子中限制輸入大小。

scanf("%9s", input); // 最多讀取9字元

或改用更安全的 fgets

fgets 的問題與解決方法

問題1: 含有換行字元

fgets 會保留輸入最後的換行符,可能導致字串比較失敗。

範例
char input[20];
fgets(input, sizeof(input), stdin);
if (strcmp(input, "yes") == 0) {
    printf("輸入為 yes。\n");
}

此時 input 內容實際為 "yes\n",比較會失敗。

解決方法

移除換行字元。

input[strcspn(input, "\n")] = '\0';

處理輸入錯誤的方法

問題1: 無效輸入

當程式期望數字,但使用者輸入字串時,程式可能異常。

範例
int number;
scanf("%d", &number); // 若輸入文字會導致錯誤
解決方法

檢查輸入是否合法。

if (scanf("%d", &number) != 1) {
    printf("輸入無效。\n");
    while (getchar() != '\n'); // 清除輸入緩衝區
}

多行輸入的問題

問題: scanffgets 混用

在同一程式中混用 scanffgets,常會出現意料之外的行為。

原因

scanf 不會移除換行符,因此下一個 fgets 會直接讀取換行。

解決方法

scanf 之後清空緩衝區。

while (getchar() != '\n');

小結

標準輸入在C語言中容易出現各種問題,但只要理解原因並妥善處理,就能建立更安全與高效的程式。特別是要理解 scanffgets 的差異,並依需求選擇合適的方法。

7. 使用標準輸入的應用程式範例

本章將介紹一些使用標準輸入的實際程式範例,從基礎到進階,幫助您透過實際代碼理解如何應用。

範例1: 計算數字總和與平均值

此程式會從標準輸入讀取多個整數,並計算總和與平均。

#include <stdio.h>

int main() {
    int numbers[100];
    int count = 0, sum = 0;
    float average;

    printf("請輸入多個整數(以空白分隔,結束請按 Ctrl+D):\n");

    while (scanf("%d", &numbers[count]) == 1) {
        sum += numbers[count];
        count++;
    }

    if (count > 0) {
        average = (float)sum / count;
        printf("總和: %d, 平均: %.2f\n", sum, average);
    } else {
        printf("未輸入任何數字。\n");
    }

    return 0;
}

範例2: 回文判斷程式

判斷輸入的字串是否為回文(正反讀都一樣)。

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool isPalindrome(char str[]) {
    int start = 0;
    int end = strlen(str) - 1;

    while (start < end) {
        if (str[start] != str[end]) {
            return false;
        }
        start++;
        end--;
    }
    return true;
}

int main() {
    char input[100];

    printf("請輸入字串: ");
    fgets(input, sizeof(input), stdin);

    // 移除換行字元
    input[strcspn(input, "\n")] = '\0';

    if (isPalindrome(input)) {
        printf("輸入的字串是回文。\n");
    } else {
        printf("輸入的字串不是回文。\n");
    }

    return 0;
}

範例3: 處理CSV格式資料

此程式會讀取CSV(逗號分隔值)資料,並逐一處理各欄位。

#include <stdio.h>
#include <string.h>

int main() {
    char input[200];
    char *token;

    printf("請輸入CSV格式資料: ");
    fgets(input, sizeof(input), stdin);

    // 移除換行字元
    input[strcspn(input, "\n")] = '\0';

    // 以逗號分割
    token = strtok(input, ",");
    while (token != NULL) {
        printf("值: %s\n", token);
        token = strtok(NULL, ",");
    }

    return 0;
}

範例4: 互動式輸入程式

此程式會多次接收使用者輸入,並依內容進行處理。

#include <stdio.h>
#include <string.h>

int main() {
    char input[50];

    printf("輸入 'exit' 可結束程式。\n");
    while (1) {
        printf("請輸入指令: ");
        fgets(input, sizeof(input), stdin);

        // 移除換行字元
        input[strcspn(input, "\n")] = '\0';

        if (strcmp(input, "exit") == 0) {
            printf("程式結束。\n");
            break;
        } else {
            printf("輸入的指令: %s\n", input);
        }
    }

    return 0;
}

小結

透過這些應用程式範例,您可以更好地理解標準輸入的用法。請嘗試將其應用到您的專案中,設計出符合需求的互動式程式。

8. 總結

本文從基礎到進階,全面解說了C語言中標準輸入的使用方式。希望透過這些內容,您能理解標準輸入的運作機制、安全處理方法,以及實際應用範例。以下簡單回顧重點。

再次確認標準輸入的重要性

  • 標準輸入是程式從外部接收資料並進行動態操作的基本功能。
  • 在C語言中,可以使用 scanffgets 讀取數字或字串。

安全且高效輸入處理的要點

  1. 防止緩衝區溢位
  • scanf 中限制輸入大小(例如 %9s)。
  • 處理長字串或逐行輸入時,優先使用 fgets
  1. 驗證輸入資料
  • 使用 strtolstrtod 等函式,檢查輸入是否合法。
  • 若有不正確輸入,需進行適當的錯誤處理。
  1. 進階處理
  • 處理多筆輸入或換行字元時,需特別注意正確性與安全性。

下一步建議

在熟悉標準輸入後,建議進一步學習以下內容,以提升程式設計能力:

  • 標準輸出與錯誤輸出:掌握如何輸出錯誤訊息與記錄日誌。
  • 檔案操作:結合標準輸入與檔案讀寫,製作更進階的程式。
  • 實作練習:嘗試修改本文範例,設計適合自己專案的輸入處理。

給讀者的話

標準輸入是C語言程式設計的基礎,但同時也蘊含許多細節與進階技巧。希望本文能幫助您在實作中養成「安全且高效處理輸入」的習慣。無論疑問多小,都應一步步解決,這將成為您程式學習旅程中的重要基石。

 

侍エンジニア塾