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 函式用於從流中讀取一個字元。
int fgetc(FILE *fp);
如果成功,fgetc 返回流中的下一個位元組或字元(取決於檔案是“二進位制”還是“文字”,如上文 fopen 中所述)。如果失敗,fgetc 返回 EOF。(可以使用 ferror 或 feof 與檔案指標一起確定具體的錯誤型別。)
標準宏 getc 也在 <stdio.h> 中定義,其行為與 fgetc 幾乎相同,只是它是一個宏,因此可能會多次評估其引數。
標準函式 getchar 也在 <stdio.h> 中定義,它不接受任何引數,等效於 getc(stdin)。
使用 fgetc、getc 或 getchar 時的一個錯誤是在將結果與 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 值匹配。另一方面,如果 EOF 在 char 的範圍內,這保證了 EOF 和 char 值之間存在衝突。因此,無論系統型別如何定義,在測試 EOF 時永遠不要使用 char 型別。
在 int 和 char 大小相同的系統上(即,與至少滿足 POSIX 和 C99 標準的系統不相容的系統),即使是“好的”示例也會受到 EOF 和某個字元的值無法區分的影響。處理這種情況的正確方法是在 getchar 返回 EOF 後檢查 feof 和 ferror。如果 feof 指示檔案結束尚未到達,並且 ferror 指示沒有發生錯誤,那麼 getchar 返回的 EOF 可以被認為代表一個實際的字元。這些額外的檢查很少進行,因為大多數程式設計師假設他們的程式碼永遠不需要在這些“大 char”系統上執行。另一種方法是使用編譯時斷言來確保 UINT_MAX > UCHAR_MAX,這至少可以防止帶有這種假設的程式在這樣的系統上編譯。
在 C 程式語言中,fread 和 fwrite 函式分別提供輸入和輸出的檔案操作。fread 和 fwrite 在 <stdio.h> 中宣告。
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 函式用於將一個字元寫入流。
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;
}
- ↑ C99 §6.2.5/15