iT邦幫忙

1

二、三天學一點點 C:來! 輸入與輸出(3)

c
  • 分享至 

  • xImage
  •  

🖨️ C 語言輸入與輸出指令

在 C 語言程式設計中,與使用者或檔案進行互動的核心是輸入 (Input) 與輸出 (Output),統稱為 I/O。C 語言透過其標準函式庫 (Standard Library) 提供了一系列豐富的 I/O 函式。這些函式大多被宣告在 <stdio.h> 這個標頭檔 (Header File) 中,因此在使用它們之前,我們幾乎總會在程式的開頭寫上 #include <stdio.h>

本章將探討幾組常用的 I/O 函式,從格式化 I/O、字串 I/O 到字元 I/O,並分析它們的原理、用法與差異。


練習一: scanfprintf(格式化 I/O)

這是 C 語言中最常用的一對 I/O 函式,它們能夠根據指定的格式來讀取和寫入多種資料型態。

  • printf(格式字串, ...): 將資料(變數、常數等)依照指定的格式轉換成文字,並輸出到標準輸出(通常是螢幕)。
  • scanf(格式字串, ...): 從標準輸入(通常是鍵盤)讀取文字,並依照指定的格式將其解析、轉換後存入指定的變數位址中。
#include <stdio.h>

int main() {
    // === 範例一:讀取與輸出多個整數 ===
    printf("請輸入三個整數 (以空格分隔): ");
    int input1, input2, input3;
    scanf("%d %d %d", &input1, &input2, &input3); // 注意變數名前的 '&'
    printf("您輸入的數字是: %d %d %d\n", input1, input2, input3);

    // === 範例二:讀取字串 ===
    printf("\n請輸入一個單字 (不含空格): ");
    char str1[20];
    scanf("%s", str1); // 讀取字串時,陣列名稱本身就是位址,不需加 '&'
    printf("您輸入的單字是: %s\n", str1);
    
    return 0;
}

📥 scanf 原理與常見行為

  • scanf 會從標準輸入(鍵盤)讀取資料,依照格式控制符(如 %d, %s)將輸入的資料存入對應變數。
  • scanf 在讀取時,預設會以空白字元 (Whitespace),包含空格、Tab、換行符等,作為資料的分隔符。當使用 %s 讀取字串時,它會在遇到第一個空白字元時停止讀取。
  • scanf 會讀取並跳過輸入緩衝區中的換行符。
  • 讀取整數時,如果沒輸入足夠個數,程式會卡在那行直到輸入完畢。
  • & 運算子:scanf 的目的是要「改變」變數的值,所以必須傳遞變數的記憶體位址給它,因此在變數名前要加上取址運算子 &。唯一的例外是字串陣列,因為陣列名稱本身就代表了其起始位址。

📤 printf 原理與常見行為

  • printf 負責格式化輸出資料到螢幕。
  • 只有在格式字串裡明確加上 \n 才會換行。
  • 可以輸出任何格式的資料(整數、字串、浮點數等)。
  • 輸出時保留空格、換行,完全由格式字串決定。

練習二:sscanfsprintf(字串格式化 I/O)

這對函式的功能與 scanf/printf 非常相似,但它們的操作對象不是標準輸入/輸出流,而是記憶體中的字串。

  • sprintf(目標字串緩衝區, 格式字串, ...): 將格式化的資料「印」到一個字串中,而不是螢幕。
  • sscanf(來源字串, 格式字串, ...): 從一個字串中「讀取」格式化的資料。
#include <stdio.h>

int main() {
    char str[10];
    int dec;
    float pi;
    // 使用 sscanf 從字串中解析資料
    char input_source[] = "abc 50 3.145";
    sscanf(input_source, "%s %d %f", str, &dec, &pi);
    printf("從字串 \"%s\" 解析出的資料:\n", input_source);
    printf("字串: %s, 整數: %d, 浮點數: %.3f\n\n", str, dec, pi);

    // 使用 sprintf 將資料格式化並存入字串
    char output_buffer[80];
    sprintf(output_buffer, "圓周率的值是: %.3f", pi);
    printf("將資料格式化後存入字串,結果為:\n");
    printf("%s\n", output_buffer);

    return 0;
}

🗃️ sscanf 原理與常見行為

  • sscanf 是「string scan formatted」,和 scanf 類似,但來源不是鍵盤輸入,而是「一段字串」。
  • 格式控制與 scanf 完全一樣。會依空白字元(空格、tab、換行)分隔不同資料欄位。
  • sscanf(str, "%d %f", &a, &b); 可從 str 字串中解析兩個欄位。
  • 不會因為有換行就停止,除非字串中真的有 \n 或結尾。

🏷️ sprintf 原理與常見行為

  • sprintf 是「string print formatted」,會將格式化後的內容寫入指定的字串陣列,而不是螢幕。
  • printf 幾乎一樣,差別在於「輸出目標」是陣列不是螢幕。
  • 使用 sprintf 時要特別小心,必須確保目標字串緩衝區 (output_buffer) 足夠大,能夠容納所有格式化後的內容,否則會造成緩衝區溢位 (Buffer Overflow),這是一個嚴重的安全漏洞。更安全的替代方案是 snprintf,它允許指定最大寫入長度。
  • 不會自動換行,必須自行在格式字串中加 \n

練習三:getsputs(行導向 I/O (注意:gets 已被棄用))

這組函式以「行」為單位來處理字串。

  • puts(字串): 輸出一行字串到標準輸出,並自動在結尾加上一個換行符 \n。
  • gets(字串緩衝區): 從標準輸入讀取一行字串,直到遇到換行符為止。
#include <stdio.h>

int main() {
    // puts 的使用
    char str_s[] = "I love coding.";
    puts("--- puts 範例 ---");
    puts(str_s); // 會輸出 I love coding. 並換行
    
    // 比較 puts 與 printf
    char str_s2[] = "I don't love coding.";
    puts("\n--- printf 範例 (無 \\n) ---");
    printf("%s", str_s2); // 只輸出 I don't love coding. 不會自動換行
    printf(" (printf 輸出的結尾)\n");
    
    /* * gets 的範例在此省略,因為它極度不安全。
     * char input_str[10];
     * gets(input_str); // 如果使用者輸入超過9個字元,就會發生緩衝區溢位!
    */
    
    return 0;
}

重點解析

  • gets 的危險性:gets 函式在讀取輸入時,完全不檢查目標緩衝區的大小。如果使用者輸入的字串長度超過了陣列的容量,多出的字元會覆寫到相鄰的記憶體,引發不可預期的行為或安全漏洞。
  • C11 標準:由於其固有的危險性,gets 函式已在 C11 標準中被正式移除。絕對不要在任何新的程式碼中使用 gets。請永遠使用 fgets 作為替代。
  • puts 的便利性:puts 會自動加上換行符,對於單純輸出整行字串的場景,比 printf 更簡潔。

練習四: fgetsfputs(安全的檔案/流導向 I/O)

這組函式是 gets 和 puts 的安全且更通用的版本,它們可以對任何檔案流 (File Stream) 進行操作,包括標準輸入 (stdin) 和標準輸出 (stdout)。

  • fgets(緩衝區, 大小, 檔案流): 從指定的流讀取一行字串。
  • fputs(字串, 檔案流): 將一個字串寫入指定的流。
#include <stdio.h>

int main() {
    // fgets 從標準輸入讀取一行
    char str_fgets[20];
    printf("請輸入一行文字 (最多19個字元): ");
    fgets(str_fgets, sizeof(str_fgets), stdin); // sizeof(str_fgets) 確保不會溢位
    printf("fgets 的讀取結果: %s", str_fgets);

    // fputs 寫入到標準輸出
    char str_puts[] = "I love you. ";
    char str_puts1[] = "And you?";
    fputs(str_puts, stdout);
    fputs(str_puts1, stdout);
    printf("\n");

    return 0;
}

重點解析

  • fgets 的安全性:fgets 最重要的特性是它的第二個參數(大小)。它告訴函式緩衝區最多能容納多少個字元。fgets 讀取時絕不會超過這個大小減一(保留一個位置給結尾的 \0),從而徹底避免了緩衝區溢位。
  • 換行符處理:fgets 會讀取並保留輸入中的換行符 \n(如果緩衝區空間足夠的話)。這就是為什麼上面範例的 printf 輸出後面通常會多一個換行。
  • fputs 的行為:與 puts 不同,fputs 不會自動在結尾添加換行符。如範例所示,兩次 fputs 的輸出會連在一起。
  • 檔案流與標頭檔:stdinstdout 是在 <stdio.h> 中定義的兩個預設檔案流,分別代表標準輸入和標準輸出。fgetsfputs 的設計使其可以輕易地用於檔案讀寫,只需將 stdin/stdout 替換成一個用 fopen() 開啟的檔案指標即可。

練習五:getchar/putchargetc/putc(字元導向 I/O)

這幾組函式是 C 語言中最基礎的 I/O,它們一次只處理一個字元。
getchar(): 等同於 getc(stdin),從標準輸入讀取一個字元。
putchar(字元): 等同於 putc(字元, stdout),將一個字元寫入標準輸出。

#include <stdio.h>

int main() {
    printf("請輸入一個字元: ");
    char c = getc(stdin);
    printf("putc 的輸出: ");
    putc(c, stdout);
    
    // 清除輸入緩衝區中的換行符
    while (getchar() != '\n'); 
    
    printf("\n\n請再輸入一個字元: ");
    char b = getchar();
    printf("putchar 的輸出: ");
    putchar(b);
    printf("\n");

    return 0;
}

重點分析:

  • 單字元處理:它們嚴格地一次只讀寫一個字元。
  • 數字也是字元:如果您輸入數字 1,getchar() 讀取到的是字元 '1'(其 ASCII 碼為 49),而不是整數值 1。
  • 原理:getchar/putchargetc/putc 針對標準輸入輸出的特化版本(巨集定義),使用上更簡潔。而 getc/putc 則更通用,可以操作任何檔案流。
  • 緩衝區問題:鍵盤輸入通常是行緩衝的,意思是您輸入的內容(包括最後的 Enter 鍵)會先被放在一個緩衝區裡。getchar 從中讀取第一個字元後,其餘的字元(包括 \n)仍會留在緩衝區中,可能會影響下一次的輸入讀取。範例中 while (getchar() != '\n'); 是一種常見的清空緩衝區的方法。

⛳ 比較與實用重點

指令 輸入/輸出 自動換行 空格行為 何時結束 備註
puts 輸出 照常輸出 \\0 每次自動加換行
printf 輸出 照常輸出 依格式字串 需手動加 \\n
fgets 輸入 - 長度或換行或結尾 換行符也會存進陣列
fputs 輸出 照常輸出 \\0 不自動加換行
getc 輸入 - 每次讀一字元 getc(stdin) = getchar()
putc 輸出 每次寫一字元 putc(c, stdout) = putchar(c)
getchar 輸入 - 每次讀一字元 只從 stdin
putchar 輸出 每次寫一字元 只到 stdout


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言