標準庫 IO
輸入輸出功能並非C語言的組成部分,ANSI標準定義了相關的庫函式
輸入輸出 <stdio.h>
流stream是與裝置關聯的資料的源或者目的地。
- 文字流:由文字行組成的序列
不同系統的特性可能不一樣,比如行最大長度和行結束符 - 二進位制流:未經處理的位元組序列
程式執行時,預設開啟 stdin, stdout, stderr
標準 IO 常量
- EOF:檔案尾,實際值是幾個字元避免混淆
- FOPEN_MAX:一個程式同時最多能夠開啟的檔案數量,與編譯器有關,值至少為 8
- FILENAM_MAX:編譯器支援的最長檔名
檔案流操作
檔案指標指向包含檔案資訊的結構,包括緩衝區,讀寫狀態等
開啟與關閉
FILE *fopen(const char *filename, const char *mode); // 開啟檔案流
/*
mode:
* r, 開啟讀
* w, 開啟或建立寫,刪除原有內容
* a, 開啟或者建立,檔案尾追加
* +, 更新,讀或寫,交叉操作前需要執行fflush或者定位操作
* r+, 開啟檔案更新(讀和寫)
* w+, 建立檔案更新,刪除原有內容
* a+, 開啟或建立檔案,檔案尾更新
* b, 二進位制流模式
應始終檢查返回值
*/
FILE *freopen(const char *filename, const char *mode, FILE *stream); // 先關閉再開啟檔案流
FILE *fdopen(int fd, const char *type); // POSIX標準,常用於由建立管道和網路通訊通道函式返回的描述符,不能直接用 fopen 開啟
// type 引數: r, w, a, (+)
// 注意讀寫模式下讀寫操作之間需要進行呼叫定位函式
int fclose(FILE *stream); // 重新整理輸出緩衝,釋放系統緩衝區,關閉流
int fileno(FILE *fp); // POSIX 標準
流的定向
標準IO可用於單位元組和多位元組字符集,具體由流的定向決定。
- freopen函式清除一個流的定向
- fwide函式設定流的定向、
#include <stdio.h>
#include <wchar.h>
int fiwde(FILE *fp, int mode);
/*
mode<0, 試圖設定為單位元組
mode>0,試圖設定為多位元組
mode=0,不設定定向,返回流的定向
不會改變已經設定的流定向
*/
檔案操縱函式
重新命名
int remove(FILE *stream); // 刪除檔案
int rename(const char *oldname, const char *newname); // 重新命名檔案
臨時檔案
FILE *tmpfile(void); // 以`wb+`模式建立臨時檔案,在關閉或者程式結束時自動刪除
// 實現上tmpnam,建立檔案,unlink(不會刪除內容),檔案的關閉會在程式結束中自動進行
char *tmpnam(char s[L_tmpnam]); // 建立不同現有檔名的字串,NULL返回指向靜態陣列的指標
// UNIX 優勢是不存在時間間隙,避免其他程序建立同名檔案
char *mkdtemp(char *temple); // 建立目錄
int mkstmp(char *temple); // 建立檔案,不會自動刪除
緩衝操作
標準IO提供緩衝的目的是儘可能減少read/write的呼叫次數
-
全緩衝:緩衝區滿了才進行實際IO操作
-
行緩衝:遇到換行符才進行IO操作
- 行緩衝的長度是固定的,行緩衝滿了即使沒有換行符也會進行IO操作
- 標準IO要求從不帶緩衝或者行緩衝(需要從核心請求資料)獲取資料,會立即重新整理所有行緩衝的輸出流
-
不帶緩衝
ISO C標準
- 當且僅當標準輸入和標準輸出不指向互動式裝置才是全緩衝的
- 標準錯誤不會是全緩衝的
預設情況
- 標準錯誤不帶緩衝
- 若是指向終端裝置的流,則是行緩衝的,否則是全緩衝的
更換緩衝的型別:流被開啟之後,且在執行任何操作之前
int fflush(FILE *stream); // 重新整理緩衝區
/*
- 對於輸出流,重新整理寫入緩衝區的內容至目標檔案
- 對於輸入流,其結果是未定義的
- NULL,重新整理所有的緩衝區
實際使用技巧:每個除錯 printf 之後立馬呼叫 fflush
*/
int setvbuf(FILE *stream, char *buf, int mode, size_t size); // 必須在執行讀寫操作之前設定緩衝
/*
mode:
- _IOFBF 完全緩衝
- _IOLBF 行緩衝
- _IONBF 不設定緩衝
*/
void setbuf(FILE *stream, char *buf); // char buf[BUFSIZ]
/*
- buff為 NULL, 關閉緩衝
- 否則,等價於 `_IOFBF`
// 注意:buf 不要使用自動變數型別,儘量使用系統緩衝區或者動態分配記憶體
*/
#TODO#檢視流緩衝狀態 《UNIX高階環境程式設計》
讀寫流操作
字元 IO
#TODO#輸入輸出函式家族 《C和指標》P301
int fgetc(FILE *stream); // unsigned char 轉 int, 相容EOF
int getc(FILE *stream); // 等價於 fgetc,注意實現為宏
int getchar(void); // 等價於 getc(stdin)
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream); // 等價於 fputc, 注意實現為宏
int putchar(int c); // 等價於 fputc(stdout)
// 只有fgetc和fputc是函式,其他都是宏
int ungetc(int c, FILE *stream); // 將字元退回流中,依賴於當前位置
// 不同於寫操作,僅涉及流本身而無關裝置儲存
未格式化的行IO
char *fgets(char *s, int n, FILE *stream); // 自動包含換行符,\n 換為 \0,最多n-1
char *gets(char *s); // 不自動包含換行符。沒有緩衝區長度引數,可能導致越界;不推薦使用,已廢棄
int fputs(const char *s, FILE *stream); // 不自動包含換行符\n,逐字元輸入任意個數換行符
int puts(const char *s); // 自動新增換行符\n
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
// 根據輸入動態分配記憶體,無需預先確定輸入字串的最大長度
// 在儲存的字串中將換行符替換為字串結束符'\0'
格式化 IO
int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...); // 等價於 `fprintf(stdout, fotmat, ...)`
int sprintf(char *s, const char *format, ...); // 包含結束符 NUL,無長度引數,可能越界溢位
int snprintf(char *buf, szie_t n, const char *format, ...); // 超出部分截斷,出錯返回負值
int fdprintf(int fd, const char *format, ...);
// 變長引數列表變體
int vprintf(const char *format, va_list arg);
int vfprintf(FILE *stream, const char *format, va_list arg);
int vsprintf(char *s, const char *format, va_list arg);
轉換格式:
-
普通字元:複製到輸出流
-
轉換說明:控制引數的格式轉換
-
開頭 %
-
標誌[可選]
-
-
左對齊,預設為右對齊 -
+
顯示正負號 - 空格 有符號值轉換
0
寬度不足時填充 0-
#
指定另一種輸出形式
-
-
寬度數值[可選]:指定最小欄位寬度
-
精度數值[可選]:點號開始,後接十進位制數值
-
長度修飾符[可選]:指定引數的長度
- h 按照short/unsigned short 輸出
- l 按照long/unsigned long 輸出
- L 按照long double 輸出
-
結尾: 轉換字元, d, c, s, f, x等
-
int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
int sscanf(const char *s, char *format, ...);
int fdscanf(int fd, const char *format,...);
// 變長引數列表變體 ...
- 引數必須是指標
- 到檔案尾或者出錯返回EOF,否則返回實際輸入的字元數
轉換格式
-
空格或者製表符
-
普通字元:匹配下一個輸入
-
轉換說明
- 開始標誌
%
[可選] - 賦值遮蔽符號
*
[可選] - 最大欄位寬度數值[可選]
- 限定符
h\l\L
,指定引數的長度[可選] - 結束標誌:轉換字元 d,f,c, s, x等等
- 開始標誌
#TODO#4種使用場景P309
二進位制 IO
直接IO/二進位制IO,通常一次處理一個結構,能夠處理null位元組和換行符。
注意事項:只能用於同一系統,不同系統的偏移對齊以及儲存格式可能不同。因此網路通訊需要指定規範。
size_t fread(void *buffer, size_t size, size_t nobj, FILE *stream);
size_t fwrite(const void *buffer, size_t size, size_t nobj, FILE *stream);
/* 返回值是實際讀寫的元素的個數而非位元組數
fread:少於nobj, 出錯或者EOF,需要進一步分辨
fwrite:少於nobj,錯誤
*/
記憶體流
無關檔案,直接在緩衝區和主存之間進行位元組IO。非常適用於字串。
Linux支援。
FILE *fmemopen(void *buf, size_t size, const char *type); // buf=null, 讀寫無意義;對於null位元組的處理十分特殊
FILE *open_memstream(char **bufp, size_t sizep); // 面向位元組的流
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep); // 面向寬位元組的流
檔案定位函式
- 二進位制檔案:使用位元組偏移量,不一定支援 SEEK_END
- 文字檔案:格式不同不能使用位元組,orgin=SEEK_SET, offset=0/ftell
int fseek(FILE *stream, long offset, int origin);
/*
- 二進位制檔案
- origin
- `SEEK_SET` 檔案開始處
- `SEEK_CUR` 當前位置
- `SEEK_END` 檔案結束處,可能不支援
- 文字檔案
- `SEEK_SET`;offset是 0 或者 `ftell`返回值
- `SEEK_CUR`/`SEEK_END`:offset只能是 0
注意事項
- 行末指示符將被清除
- 退回的字元將被丟棄
- 更新模式中的讀寫操作切換
*/
long ftell(FILE *stream);
void rewind(FILE *stream); // 重置為起始位置
//等價於 `fseek(stream, 0L, SEEK_SET); clearerr(stream);`
// off_t, 大於32位 UNIX 標準
off_t ftello(FILE *fp);
int seeko(FILE *fp, off_t offset, int origin);
// fpos_t ISO C標準,更加通用
int fgetpos(FILE *stream, fpos_t *ptr);
int fsetpos(FILE *stream, const fpos_t *ptr);
錯誤處理函式
發生錯誤或者到達檔案尾時會設定狀態指示符
整型表示式 errno 包含錯誤編號,定義在 <errno.h>
- 只有庫函式失敗時才會設定 errno, 成功執行並不會修改 errno
- 任何函式都不會將常量置為0
/* ----- 流錯誤 -----*/
int feof(FILE *stream); // 流設定了檔案結束指示符,返回非0值
int ferror(FILE *stream); // 流設定了錯誤指示符,返回非0值
int clearerr(FILE *stream); // 清除流的所有指示符
#include <string.h>
char *strerror(int crrno); // 對映錯誤資訊
#include <stdio.h>
int perror(const char *s); // 列印字串和 errno 錯誤資訊
// 類似於 fprintf(stderr, "%s: %s\n", s, "error essage");
標準IO的替代
標準IO 的效率不高,呼叫行IO需要進行兩次資料的複製
- 快速IO fio: 使用指標而不是複製整行
- sfio: 提高速度,同時推廣IO流
- mmap
適用於嵌入式系統的更低記憶體要求的實現
- uClibc C庫
- Newlib C庫
參考
- 《C程式設計語言》
- 《UNIX 環境高階程式設計》