C 語言輸入處理全攻略:從基礎到進階的安全與實用技巧

目次

1. 前言:什麼是 C 語言的輸入?

C 語言是應用最廣泛的程式語言之一,在系統開發與嵌入式系統中扮演著重要角色。
其中,「輸入處理」是從使用者取得資料並反映到程式中不可或缺的功能。
本文將從基礎到進階,詳細解說 C 語言的輸入處理,並提供對初學者到中級開發者都實用的知識。

C 語言輸入的作用

C 語言中的輸入主要有以下用途:

  1. 使用者資料輸入:由終端機(主控台)讓使用者輸入數值或字串。
  2. 檔案讀取:從外部檔案取得資料並進行處理。
  3. 資料驗證與加工:檢查輸入資料,並在必要時進行修正或加工。

例如,依據使用者輸入的數值進行計算的程式,或是從檔案中讀取客戶資訊的系統,都屬於此類應用。

輸入處理的重要性

輸入處理直接關係到程式的安全性與可靠性,特別需要注意以下幾點:

  • 錯誤處理:適當處理輸入錯誤或非預期資料造成的錯誤,以防止程式崩潰。
  • 安全性保障:使用安全的函式以防止像是緩衝區溢位等安全漏洞。
  • 支援多種資料格式:具備可處理數值、字串、檔案等多樣資料型態的彈性設計。

文章目的與內容

本文將依以下步驟解說 C 語言的輸入處理基礎與應用:

  1. 標準輸入與標準輸出的運作原理
  2. 基本輸入函式的用法與安全性考量
  3. 進階輸入處理與檔案操作
  4. 錯誤處理與多位元組文字支援

另外,會搭配實際範例程式碼,提供具體使用案例。
目標是讓初學者容易理解,並為中級開發者提供進一步應用的啟發。

下一步

接下來的章節將詳細解說 C 語言中標準輸入與標準輸出的基礎。
讓我們先打好輸入處理的基礎,邁出撰寫安全程式碼的第一步。

2. 基本的輸入處理與函式用法

在 C 語言中,為了進行輸入處理,標準函式庫提供了多種函式。本節將說明標準輸入與標準輸出的運作原理,並解說各種函式的具體用法。

2.1 標準輸入與標準輸出的運作原理

在 C 語言中,「標準輸入」是指從鍵盤等裝置接收資料的機制。同樣地,「標準輸出」是指將結果顯示在螢幕上的機制。

標準輸入 (stdin) 與標準輸出 (stdout) 概要

  • 標準輸入 (stdin):用來接收使用者從鍵盤輸入的資料。
  • 標準輸出 (stdout):用來在螢幕上輸出接收的資料或處理結果。

這些功能定義於標準函式庫 <stdio.h>,可在程式中自由使用。

基本程式範例

以下是一個從標準輸入讀取一個整數,並在標準輸出顯示該值的範例:

#include <stdio.h>

int main() {
    int number;

    printf("請輸入一個整數: ");
    scanf("%d", &number); // 從標準輸入讀取整數
    printf("輸入的值是 %d。\n", number); // 在標準輸出顯示結果

    return 0;
}

這個程式會將使用者透過鍵盤輸入的數值存入變數 number,並輸出到螢幕。

2.2 使用 scanf 函式進行輸入

scanf 函式的基本語法

scanf("格式指定子", 位址);

格式指定子用於指定輸入資料的型別。常用的指定子如下:

格式指定子資料型別說明
%dint整數
%ffloat浮點數
%lfdouble雙精度浮點數
%cchar單一字元
%schar 陣列字串

實例:同時輸入多筆資料

#include <stdio.h>

int main() {
    int age;
    float height;

    printf("請以空格分隔輸入年齡與身高: ");
    scanf("%d %f", &age, &height); // 輸入整數與浮點數
    printf("年齡: %d, 身高: %.2f\n", age, height);

    return 0;
}

此程式可同時輸入年齡與身高,分別存入對應的變數,並輸出結果。

注意事項:緩衝區溢位

scanf 容易因為輸入超出預期長度而發生緩衝區溢位。特別是讀取字串時,若未限制長度,可能破壞記憶體。

2.3 字串輸入與安全處理

gets 函式已不建議使用

舊版 C 語言常用 gets 讀取字串,但它無法防止緩衝區溢位,因此已不建議使用。

安全替代方案:fgets

現在推薦使用 fgets 安全讀取字串:

#include <stdio.h>

int main() {
    char name[50];

    printf("請輸入姓名: ");
    fgets(name, sizeof(name), stdin); // 安全讀取字串
    printf("輸入的姓名是: %s", name);

    return 0;
}

重點fgets 可限制輸入大小,避免緩衝區溢位。

去除字串中的換行符號

name[strcspn(name, "\n")] = '\0'; // 去除換行符號

2.4 輸入錯誤處理

檢測非法輸入

#include <stdio.h>

int main() {
    int number;

    printf("請輸入一個整數: ");
    if (scanf("%d", &number) != 1) { // 確認是否正確讀取到一筆輸入
        printf("輸入無效。\n");
        return 1; // 錯誤結束
    }

    printf("輸入的值: %d\n", number);
    return 0;
}

此程式會在輸入非整數時提示錯誤並結束執行。

3. 進階輸入處理的解說

本節將解說 C 語言中更進階的輸入處理,包括檔案讀取、錯誤處理以及數值轉換的詳細方法。

3.1 從檔案進行輸入處理

在 C 語言中,除了標準輸入之外,從檔案讀取資料也是常見且重要的功能。當程式需要使用外部資料時,檔案輸入會非常有用。

開啟與關閉檔案

處理檔案時,需先使用 fopen 開啟檔案,處理完畢後用 fclose 關閉檔案。

#include <stdio.h>

int main() {
    FILE *file; // 宣告檔案指標
    file = fopen("data.txt", "r"); // 以唯讀模式開啟檔案

    if (file == NULL) { // 錯誤檢查
        printf("無法開啟檔案。\n");
        return 1;
    }

    printf("檔案成功開啟。\n");

    fclose(file); // 關閉檔案
    return 0;
}

如果檔案不存在,程式會顯示錯誤訊息並結束。

使用 fscanf 進行檔案輸入

fscanf 可根據指定格式從檔案讀取資料。

#include <stdio.h>

int main() {
    FILE *file;
    int id;
    char name[50];

    file = fopen("data.txt", "r");
    if (file == NULL) {
        printf("無法開啟檔案。\n");
        return 1;
    }

    while (fscanf(file, "%d %s", &id, name) != EOF) { // 讀取直到 EOF
        printf("ID: %d, 姓名: %s\n", id, name);
    }

    fclose(file);
    return 0;
}

此範例會從 data.txt 順序讀取整數與字串。

3.2 輸入資料的驗證與錯誤處理

輸入的資料不一定正確,因此錯誤處理是撰寫安全程式的重要步驟。

檢測無效資料

#include <stdio.h>

int main() {
    int number;
    printf("請輸入一個整數: ");

    while (scanf("%d", &number) != 1) { // 格式不正確
        printf("輸入無效,請重新輸入: ");
        while (getchar() != '\n'); // 清空輸入緩衝區
    }

    printf("輸入的整數為 %d。\n", number);
    return 0;
}

當偵測到無效輸入時,會要求使用者重新輸入。

3.3 數值轉換與格式指定

程式中經常需要將字串轉換為數值,C 語言可使用 strtolstrtod 等函式進行處理。

將字串轉換為整數 (strtol)

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

int main() {
    char input[20];
    char *endptr;
    long value;

    printf("請輸入數值: ");
    fgets(input, sizeof(input), stdin);

    value = strtol(input, &endptr, 10); // 以十進位轉換

    if (*endptr != '\0' && *endptr != '\n') { // 檢查轉換是否正確
        printf("無效的數值。\n");
    } else {
        printf("輸入的值為 %ld。\n", value);
    }

    return 0;
}

將字串轉換為浮點數 (strtod)

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

int main() {
    char input[20];
    char *endptr;
    double value;

    printf("請輸入數值: ");
    fgets(input, sizeof(input), stdin);

    value = strtod(input, &endptr); // 轉換為浮點數

    if (*endptr != '\0' && *endptr != '\n') {
        printf("無效的數值。\n");
    } else {
        printf("輸入的值為 %.2f。\n", value);
    }

    return 0;
}

此範例可處理包含小數點的數值。

4. 日文與多位元組文字的輸入處理

本節將解說如何處理包含日文在內的多位元組文字。要正確處理日文等非 ASCII 字元,必須理解字元編碼並使用合適的函式。

4.1 處理日文的準備工作

字元編碼與編碼方式的差異

處理日文時,需要正確設定字元編碼與編碼方式。常見的編碼如下:

字元編碼特點
UTF-8全球通用的字元編碼,廣泛支援於多數系統與平台。
Shift_JIS在日本國內過去常用的字元編碼,與舊環境的相容性高。
EUC-JP在 UNIX 系統中廣泛使用的日文編碼。

在進行國際化時,建議使用 UTF-8

設定地區 (Locale)

為了正確處理日文,必須設定適當的地區參數 (Locale)。以下範例將地區設定為日文:

#include <stdio.h>
#include <locale.h>

int main() {
    setlocale(LC_ALL, "ja_JP.UTF-8"); // 設定日文地區

    printf("地區設定完成。\n");
    return 0;
}

設定後,程式將能更好地處理日文文字與編碼。

4.2 寬字元與 wchar_t 的應用

C 語言提供寬字元型別來處理多位元組文字,使用 wchar_t 型別可儲存比 char 更多的資料。

寬字元的輸入與輸出

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
    wchar_t name[50];
    setlocale(LC_ALL, "ja_JP.UTF-8"); // 設定日文地區

    wprintf(L"請輸入姓名: ");
    fgetws(name, sizeof(name) / sizeof(wchar_t), stdin);
    wprintf(L"輸入的姓名是: %ls\n", name);

    return 0;
}

程式重點

  1. setlocale 的使用:為了正確處理日文輸入,必須設定地區。
  2. wchar_t 型別:用於儲存寬字元。
  3. wprintffgetws:專用於寬字元的 I/O 函式,可安全處理日文與其他多位元組文字。

4.3 多位元組文字的處理

多位元組文字與位元組數的計算

多位元組文字一個字可能佔用多個位元組,因此計算字元數或位元組數時需使用專用函式。

#include <stdio.h>
#include <locale.h>
#include <wchar.h>

int main() {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    char str[] = "こんにちは"; // 多位元組字串
    int length = mbstowcs(NULL, str, 0); // 計算字元數

    printf("字元數: %d\n", length);
    return 0;
}

4.4 多位元組文字的錯誤處理

檢測無效字元編碼

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>

int main() {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    char input[100];
    wchar_t output[100];
    printf("請輸入字串: ");
    fgets(input, sizeof(input), stdin);

    if (mbstowcs(output, input, 100) == (size_t)-1) {
        printf("檢測到無效的字元編碼。\n");
        return 1;
    }

    wprintf(L"轉換結果: %ls\n", output);
    return 0;
}

此程式使用 mbstowcs 檢測並處理無效的字元編碼。

5. 實例:整合性輸入程式的建立

本節將綜合前面所學,建立一個實用輸入程式,結合整數、浮點數、字串輸入與驗證、檔案操作,以及日文支援。

5.1 範例 1:多資料輸入與驗證

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

int main() {
    int age;
    float height;
    char name[50];

    printf("請輸入姓名: ");
    fgets(name, sizeof(name), stdin);
    name[strcspn(name, "\n")] = '\0';

    printf("請輸入年齡: ");
    while (scanf("%d", &age) != 1 || age < 0) {
        printf("輸入無效,請重新輸入: ");
        while (getchar() != '\n');
    }

    printf("請輸入身高(cm): ");
    while (scanf("%f", &height) != 1 || height < 0) {
        printf("輸入無效,請重新輸入: ");
        while (getchar() != '\n');
    }

    printf("姓名: %s\n", name);
    printf("年齡: %d 歲\n", age);
    printf("身高: %.2f cm\n", height);

    return 0;
}

5.2 範例 2:從檔案讀取資料

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

int main() {
    FILE *file;
    int id;
    char name[50];
    float score;

    file = fopen("data.txt", "r");
    if (file == NULL) {
        printf("無法開啟檔案。\n");
        return 1;
    }

    printf("資料列表:\n");

    while (fscanf(file, "%d %s %f", &id, name, &score) == 3) {
        printf("ID: %d, 姓名: %s, 分數: %.2f\n", id, name, score);
    }

    fclose(file);
    return 0;
}

5.3 範例 3:支援日文輸入

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
    FILE *file;
    wchar_t name[50];

    setlocale(LC_ALL, "ja_JP.UTF-8");

    wprintf(L"請輸入姓名: ");
    fgetws(name, sizeof(name) / sizeof(wchar_t), stdin);

    name[wcslen(name) - 1] = L'\0';

    file = fopen("output.txt", "w");
    if (file == NULL) {
        wprintf(L"無法開啟檔案。\n");
        return 1;
    }

    fwprintf(file, L"姓名: %ls\n", name);
    fclose(file);

    wprintf(L"資料已儲存。\n");
    return 0;
}

6. 常見錯誤與疑難排解

本節將介紹 C 語言輸入處理中常見的錯誤與問題,並提供具體的解決方法。

6.1 緩衝區溢位 (Buffer Overflow)

問題概述

使用 scanf 等函式時,如果輸入長度超過預期,就可能發生緩衝區溢位,導致程式出現異常行為。

發生範例

#include <stdio.h>

int main() {
    char buffer[10];
    printf("請輸入姓名: ");
    scanf("%s", buffer); // 若輸入超過 10 字元,會溢位
    printf("姓名: %s\n", buffer);
    return 0;
}

輸入超過緩衝區大小時,記憶體可能被破壞。

解決方案:使用 fgets

#include <stdio.h>

int main() {
    char buffer[10];
    printf("請輸入姓名: ");
    fgets(buffer, sizeof(buffer), stdin); // 限制輸入長度
    printf("姓名: %s\n", buffer);
    return 0;
}

透過限制輸入大小,可避免溢位。

6.2 輸入緩衝區殘留資料

問題概述

使用 scanf 時,換行符號或空白可能殘留在緩衝區,導致下一次輸入被跳過。

發生範例

#include <stdio.h>

int main() {
    int age;
    char name[50];

    printf("請輸入年齡: ");
    scanf("%d", &age);
    printf("請輸入姓名: ");
    fgets(name, sizeof(name), stdin); // 會讀到殘留的換行
    printf("姓名: %s\n", name);
}

解決方案:清除緩衝區

#include <stdio.h>

int main() {
    int age;
    char name[50];

    printf("請輸入年齡: ");
    scanf("%d", &age);
    while (getchar() != '\n'); // 清除殘留資料

    printf("請輸入姓名: ");
    fgets(name, sizeof(name), stdin);
    printf("姓名: %s\n", name);

    return 0;
}

6.3 數值轉換錯誤

問題概述

將字串轉換為數值時,如果包含無效字元,可能導致錯誤或不正確的值。

發生範例

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

int main() {
    char input[10];
    int number;

    printf("請輸入數值: ");
    fgets(input, sizeof(input), stdin);
    number = atoi(input); // 無效字串會回傳 0
    printf("輸入的數值: %d\n", number);
}

解決方案:使用 strtol 檢查

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

int main() {
    char input[10];
    char *endptr;
    long number;

    printf("請輸入數值: ");
    fgets(input, sizeof(input), stdin);
    number = strtol(input, &endptr, 10);

    if (*endptr != '\0' && *endptr != '\n') {
        printf("無效的數值。\n");
    } else {
        printf("輸入的值為 %ld。\n", number);
    }

    return 0;
}

6.4 日文亂碼

問題概述

若編碼設定不正確,處理日文時可能出現亂碼。

發生範例

#include <stdio.h>

int main() {
    char name[50];
    printf("請輸入姓名: ");
    fgets(name, sizeof(name), stdin);
    printf("姓名: %s\n", name);
}

解決方案:設定地區與使用寬字元

#include <stdio.h>
#include <locale.h>
#include <wchar.h>

int main() {
    wchar_t name[50];
    setlocale(LC_ALL, "ja_JP.UTF-8");

    wprintf(L"請輸入姓名: ");
    fgetws(name, sizeof(name) / sizeof(wchar_t), stdin);
    wprintf(L"姓名: %ls\n", name);

    return 0;
}

7. 總結與下一步

本文從 C 語言輸入處理的基礎到進階應用,涵蓋錯誤處理、日文支援與疑難排解,提供完整的學習路線。

7.1 重點回顧

1. 基礎輸入處理

  • 理解標準輸入與輸出的運作方式,並掌握 scanffgets 的用法。
  • 學會錯誤處理與避免緩衝區溢位。

2. 進階輸入處理

  • 熟悉檔案讀取、資料驗證與數值轉換技巧。

3. 多位元組文字處理

  • 能正確處理日文與多語系文字,理解 wchar_t 與地區設定。

4. 實例應用

  • 將整數、浮點數、字串、檔案與日文支援整合到一個完整程式。

5. 疑難排解

  • 處理緩衝區溢位、殘留資料、數值轉換錯誤與亂碼問題。

7.2 下一步學習建議

  1. 陣列與指標:深入理解輸入處理中的記憶體管理與動態陣列。
  2. 結構體與檔案操作:用結構體管理複雜資料並強化檔案存取。
  3. 函式與模組化:提升程式的可讀性與重用性。
  4. 進階錯誤處理:引入日誌與例外處理機制。
  5. 多執行緒編程:提升輸入處理的效率。
  6. 跨語言與網路程式設計:結合其他語言或網路功能開發應用。

7.3 實作建議

  • 親手輸入並運行範例程式,加深理解。
  • 善用標準函式庫文件查詢不熟悉的用法。
  • 由小型專案逐步擴展到大型專案。
  • 面對錯誤訊息時,先分析後解決,累積經驗。

7.4 結語

透過本篇文章的學習,你已經掌握 C 語言輸入處理的基礎與進階技巧。C 語言雖然簡潔,但功能強大,只要靈活運用,就能開發出各種實用且高效的應用程式。

接下來,請將這些知識應用到實際專案中,不斷優化程式,並探索更多 C 語言的可能性。