跳轉到內容

C 程式設計/stdio.h

來自華夏公益教科書

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

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

流式檔案 I/O 模型是由 Unix 作業系統推廣的,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 的範圍內,則這保證了 EOFchar 值之間的衝突。因此,無論系統型別如何定義,在測試 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
華夏公益教科書