跳轉到內容

C 程式設計/stdio.h

來自華夏公益教科書,開放書籍,構建開放世界

C 程式語言為檔案輸入和輸出提供了許多標準庫函式。這些函式構成了 C 標準庫標頭檔案 <stdio.h> 的大部分內容。

C 的 I/O 功能按現代標準來說是相當底層的;C 將所有檔案操作抽象成位元組流操作,這些位元組流可以是“輸入流”或“輸出流”。與一些早期的程式語言不同,C 不直接支援隨機訪問資料檔案;要從檔案中間的一個記錄中讀取資料,程式設計師必須建立一個流,定位到檔案的中間,然後從流中按順序讀取位元組。

流式檔案 I/O 模型是由 Unix 作業系統推廣的,該作業系統與 C 程式語言本身是同時開發的。絕大多數現代作業系統都從 Unix 中繼承了流,並且 C 程式語言家族中的許多語言都繼承了 C 的檔案 I/O 介面,幾乎沒有或根本沒有變化(例如,PHP)。C++ 標準庫在其語法中反映了“流”的概念;參見 iostream。

函式概述

[編輯 | 編輯原始碼]

大多數 C 檔案輸入/輸出函式都在/stdio.h (/cstdio標頭檔案中定義(在 C++ 中)。

檔案訪問
  • fopen- 開啟一個檔案
  • freopen- 用現有流開啟另一個檔案
  • fflush- 將輸出流與實際檔案同步
  • fclose- 關閉一個檔案
  • setbuf- 設定檔案流的緩衝區
  • setvbuf- 設定檔案流的緩衝區及其大小
  • fwide- 在寬字元 I/O 和窄字元 I/O 之間切換檔案流
直接輸入/輸出
  • fread- 從檔案中讀取
  • fwrite- 寫入檔案
非格式化輸入/輸出
窄字元
  • fgetc, getc- 從檔案流中讀取一個字元
  • fgets- 從檔案流中讀取一個字元字串
  • fputc, putc- 將一個字元寫入檔案流
  • fputs- 將一個字元字串寫入檔案流
  • getchar- 從 stdin 讀取一個字元
  • gets- 從 stdin 讀取一個字元字串
  • putchar- 將一個字元寫入 stdout
  • puts- 將一個字元字串寫入 stdout
  • ungetc- 將一個字元放回檔案流
寬字元
  • fgetwc, getwc- 從檔案流中讀取一個寬字元
  • fgetws- 從檔案流中讀取一個寬字元字串
  • fputwc, putwc- 將一個寬字元寫入檔案流
  • fputws- 將一個寬字元字串寫入檔案流
  • getwchar- 從 stdin 讀取一個寬字元
  • putwchar- 將一個寬字元寫入 stdout
  • ungetwc- 將一個寬字元放回檔案流
格式化輸入/輸出
窄字元
  • scanf, fscanf, sscanf- 從 stdin、檔案流或緩衝區中讀取格式化的輸入
  • vscanf, vfscanf, wsscanf- 使用可變引數列表從 stdin、檔案流或緩衝區中讀取格式化的輸入
  • printf, fprintf, sprintf, snprintf- 將格式化的輸出列印到 stdout、檔案流或緩衝區
  • vprintf, vfprintf, vsprintf, vsnprintf- 使用可變引數列表將格式化的輸出列印到 stdout、檔案流或緩衝區
寬字元
  • wscanf, fwscanf, swscanf- 從 stdin、檔案流或緩衝區中讀取格式化的寬字元輸入
  • vwscanf, vfwscanf, vswscanf- 使用可變引數列表從 stdin、檔案流或緩衝區中讀取格式化的寬字元輸入
  • wprintf, fwprintf, swprintf- 將格式化的寬字元輸出列印到 stdout、檔案流或緩衝區
  • vwprintf, vfwprintf, vswprintf- 使用可變引數列表將格式化的寬字元輸出列印到 stdout、檔案流或緩衝區
檔案定位
  • ftell- 返回當前檔案位置指示器
  • fgetpos- 獲取檔案位置指示器
  • fseek- 將檔案位置指示器移動到檔案中特定位置
  • fsetpos- 將檔案位置指示器移動到檔案中特定位置
  • rewind- 將檔案位置指示器移動到檔案開頭
錯誤處理
  • clearerr- 清除錯誤
  • feof- 檢查檔案結束
  • ferror- 檢查檔案錯誤
  • perror- 將對應於當前錯誤的字元字串顯示到 stderr
檔案操作
  • remove- 刪除一個檔案
  • rename- 重新命名一個檔案
  • tmpfile- 返回指向臨時檔案的指標
  • tmpnam- 返回一個唯一的檔名

使用 fgetc 從流中讀取

[編輯 | 編輯原始碼]

fgetc 函式用於從流中讀取一個字元。

int fgetc(FILE *fp);

如果成功,fgetc 返回流中的下一個位元組或字元(取決於檔案是“二進位制”還是“文字”,如上文 fopen 中所述)。如果失敗,fgetc 返回 EOF。(可以使用 ferrorfeof 與檔案指標一起確定具體的錯誤型別。)

標準宏 getc 也在 <stdio.h> 中定義,其行為與 fgetc 幾乎相同,只是它是一個宏,因此可能會多次評估其引數。

標準函式 getchar 也在 <stdio.h> 中定義,它不接受任何引數,等效於 getc(stdin)

EOF 陷阱

[編輯 | 編輯原始碼]

使用 fgetcgetcgetchar 時的一個錯誤是在將結果與 EOF 進行比較之前將其賦值給型別為 char 的變數。以下程式碼片段展示了這個錯誤,然後展示了正確的做法(使用型別 int)

錯誤 更正
char c;
while ((c = getchar()) != EOF)
    putchar(c);
int c;  /* This will hold the EOF value */
while ((c = getchar()) != EOF)
    putchar(c);

假設一個系統中 char 型別為 8 位寬,表示 256 個不同的值。getchar 可能返回 256 個可能的字元中的任何一個,它也可能返回 EOF 以指示檔案結束,總共 257 個不同的返回值。

getchar 的結果被賦值給一個 char 時,它只能表示 256 個不同的值,因此必然會丟失一些資訊——將 257 個專案打包到 256 個插槽中,必然會出現衝突。EOF 值在轉換為 char 時,將變得與共享其數值的 256 個字元中的任何一個無法區分。如果該字元出現在檔案中,上面的示例可能會將其誤認為檔案結束指示器;或者,同樣糟糕的是,如果 char 型別是無符號的,那麼由於 EOF 是負數,它永遠不可能等於任何無符號 char,因此上面的示例在檔案結束時不會終止。它將永遠迴圈,重複列印將 EOF 轉換為 char 的結果的字元。

然而,如果 char 定義是有符號的(C 使預設 char 型別實現依賴於系統),[1] 假設常用的 EOF 值為 -1,則不會出現這種迴圈失敗模式。但是,根本問題仍然存在,如果 EOF 值定義在 char 類型範圍之外,則在賦值給 char 時,該值會被切片,並且不再與退出迴圈所需的完整 EOF 值匹配。另一方面,如果 EOFchar 的範圍內,這保證了 EOF 和 char 值之間存在衝突。因此,無論系統型別如何定義,在測試 EOF 時永遠不要使用 char 型別。

intchar 大小相同的系統上(即,與至少滿足 POSIX 和 C99 標準的系統不相容的系統),即使是“好的”示例也會受到 EOF 和某個字元的值無法區分的影響。處理這種情況的正確方法是在 getchar 返回 EOF 後檢查 feofferror。如果 feof 指示檔案結束尚未到達,並且 ferror 指示沒有發生錯誤,那麼 getchar 返回的 EOF 可以被認為代表一個實際的字元。這些額外的檢查很少進行,因為大多數程式設計師假設他們的程式碼永遠不需要在這些“大 char”系統上執行。另一種方法是使用編譯時斷言來確保 UINT_MAX > UCHAR_MAX,這至少可以防止帶有這種假設的程式在這樣的系統上編譯。

在 C 程式語言中,freadfwrite 函式分別提供輸入和輸出的檔案操作。freadfwrite<stdio.h> 中宣告。

使用 fwrite 寫入檔案

[編輯 | 編輯原始碼]

fwrite 的定義如下

size_t fwrite (const void *array, size_t size, size_t count, FILE *stream);

fwrite 函式將資料塊寫入流。它將寫入一個包含 count 個元素的陣列到流中的當前位置。對於每個元素,它將寫入 size 位元組。流的位置指示器將根據成功寫入的位元組數向前移動。

該函式將返回成功寫入的元素數量。如果寫入成功完成,返回值將等於 count。如果發生寫入錯誤,返回值將小於 count

以下程式開啟一個名為sample.txt的檔案,將一串字元寫入檔案,然後關閉它。

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

int main(void)
{
    FILE *fp;
    size_t count;
    const char *str = "hello\n";

    fp = fopen("sample.txt", "w");
    if(fp == NULL) {
        perror("failed to open sample.txt");
        return EXIT_FAILURE;
    }
    count = fwrite(str, 1, strlen(str), fp);
    printf("Wrote %zu bytes. fclose(fp) %s.\n", count, fclose(fp) == 0 ? "succeeded" : "failed");
    return EXIT_SUCCESS;
}

使用 fputc 寫入流

[編輯 | 編輯原始碼]

fputc 函式用於將一個字元寫入流。

int fputc(int c, FILE *fp);

引數 c 在輸出之前會默默地轉換為 unsigned char。如果成功,fputc 將返回寫入的字元。如果失敗,fputc 將返回 EOF

標準宏 putc 也在 <stdio.h> 中定義,其行為與 fputc 幾乎相同,只是作為宏,它可能會多次計算其引數。

標準函式 putchar 也在 <stdio.h> 中定義,它只接受第一個引數,等效於 putc(c, stdout),其中 c 是該引數。

示例用法

[編輯 | 編輯原始碼]

以下 C 程式開啟一個名為 myfile 的二進位制檔案,從中讀取五個位元組,然後關閉該檔案。

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

int main(void)
{
  const int count = 5;  /* count of bytes to read from file */
  char buffer[count] = {0};  /* initialized to zeroes */
  int i, rc;
  FILE *fp = fopen("myfile", "rb");
  if (fp == NULL) {
    perror("Failed to open file \"myfile\"");
    return EXIT_FAILURE;
  }
  for (i = 0; (rc = getc(fp)) != EOF && i < count; buffer[i++] = rc)
    ;
  fclose(fp);
  if (i == count) {
    puts("The bytes read were...");
    for (i = 0; i < count; i++)
      printf("%x ", buffer[i]);
    puts("");
  } else
    fputs("There was an error reading the file.\n", stderr);
  return EXIT_SUCCESS;
}

參考文獻

[編輯 | 編輯原始碼]
  1. C99 §6.2.5/15
華夏公益教科書