1、引言
標準IO庫有ISO C標準說明
- 標準IO庫處理很多細節:包括緩衝區分配、優化的塊長度執行IO
2、流和FILE對象
- 當用標準IO庫打開或創建一個文件時,已使一個流與一個文件相關聯
- 對於ASCII字符集,一個字符用一個字節表示,對於國際字符集,一個字符可用多個字節表示。標準IO文件流可用單字節或多字節字符集。
流的定向
決定了所讀寫的字符是單字節還是多字節(創建時未定向),若在未定向的流上使用多字節IO,則將流設置爲寬定向的,用單字節IO則設置爲字節定向的
#include <wchar.h>
int fwide(FILE *stream, int mode); //設置流定向
- mode:
- 爲負則設置爲字節定向
- 爲正則指定爲寬定向
- 0不設置但返回流的定向值
- 注意:fwide不改變已定向的流,且無出錯返回
3、標準輸入/輸出/錯誤
- 對一個進程預定義了stdin、stdout、stderr與前面提到的STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO所引用的相同
4、緩衝
- 目的是儘可能減少使用read()、write()調用次數
- 全緩衝:填滿緩衝區後才進行IO操作,在一個流上執行第一次IO操作時,常調用malloc獲得需使用的緩衝區
- 也可調用fflush()沖洗一個流。標準IO庫意味着沖洗到磁盤,終端驅動程序意味着丟棄緩衝區中的數據
- 行緩衝:遇到換行符時,執行IO操作,涉及到終端時常使用行緩衝
- 滿了也會執行
- 任何時候從一個不帶緩衝的流或一個行緩衝的流得到輸入數據也執行IO
- 不帶緩衝:標準IO不對字符進行緩衝存取(如fputs)
- stderr通常不帶緩衝
- 全緩衝:填滿緩衝區後才進行IO操作,在一個流上執行第一次IO操作時,常調用malloc獲得需使用的緩衝區
- ISO C要求:
- 僅當標準輸入輸出不指向交互式設備時,它們纔是全緩衝
- 標準錯誤絕不會是全緩衝
- 很多系統默認:
- 標準錯誤不帶緩衝
- 若是指向終端設備的流,則是行緩衝的,否則是全緩衝的
- 可更改緩衝類型:
#include <stdio.h> void setbuf(FILE *stream, char *buf); void setbuffer(FILE *stream, char *buf, size_t size); void setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
- 函數需要在打開流之後,指向IO操作前調用
- 使用setbuf時,buf必須指向一個BUFSIZE長度的緩衝區,關閉可設爲NULL
- setvbuf可精確說明:
- _IOFBF:全緩衝
- _IOLBF:行緩衝
- _IONBF:不帶緩衝
- 任何時候都可以強制沖洗一個流
int fflush(FILE *stream);
5、打開流
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
FILE *fdopen(int fd, const char *mode);
- freopen() 函數在一個指定的流上打開一個指定的文件。已打開則先關閉,已定向則清除。一般用於將指定文件打開爲預定義的流(stdin、stdout…)
- fdopen() 取一個已有的文件描述符,並使一個IO流與之結合。
- fdopen()寫方式打開時並不截斷文件(因爲已經打開),
- mode中有加號時:
- 如果中間沒有fflush()、fseek()、fsetpos()、rewind(),則輸出後面不能跟輸入
- 如果中間沒有fflush()、fsetpos()、rewind(),或輸入操作沒有的達到文件尾,則輸入操作後不能跟輸出
int fclose(FILE *stream);
- 文件關閉前,沖洗緩衝區中的輸出數據,並釋放緩衝區
- 進程正常終止時,所有帶未寫緩衝數據的標準IO流被沖洗,所有打開的標準IO流被關閉
6、讀和寫流
- 有三種不同類型的非格式化IO
- 每次一個字符的IO:帶緩衝則標準IO處理所有緩衝
- 每次一行的IO:以換行符終止,fgets()應說明能處理的最大行長
- 直接IO:每次IO操作讀寫某種數量的對象
6.1、輸入函數
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
- getc()可以實現爲宏,fgetc不能
- getc()參數不應該時具有副作用的表達式
- fgetc()是函數,所以可以得到其地址
- fgetc()的時間要長
- 需要注意的是,這裏的返回值是int,這樣是爲了返回所有可能的字符加一個已出錯或文件尾指示值。
- 爲了區分出錯和文件尾:
int ferror(FILE *stream); int feof(FILE *stream); void clearerr(FILE *stream);
- 大多數實現爲FILE維護兩個標誌:出錯標誌和文件結束標誌
- 從流中讀取數據後,也可以再將字符再壓送回流中:
int ungetc(int c, FILE *stream);
- 再讀出的順序與壓送的順序相反
- 壓送並沒有寫到底層文件或設備,只是寫到緩衝中
6.2、輸出函數
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
- putc()可以實現爲宏而fputc()不能
7、每次一行IO
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
- 函數都指定了緩衝區地址,讀入將送入其中
- fgets()讀入不超過n-1個字符直到換行符,緩衝區總是以null結尾
- gets是一個不推薦的函數,且不讀入換行符
int fputs(const char *s, FILE *stream);
int puts(const char *s);
- 提供每次輸出一行的功能
- fputs將null結尾的字符串寫到指定流
- puts將null結尾的字符串寫到標準輸出流,再輸出一個換行符
8、標準IO的效率
9、二進制IO
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
- 不同環境下兩個函數可能不能正常工作
- 在一個結構中,偏移量可能不同
- 用來存儲多字節整數和浮點值得二進制格式在不同的系統結構間可能不同
10、定位流
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
- whence的值與lseek相同,但是ISO C並沒要求一個實現對二進制文件支持SEEK_END規格說明。
- 對於文本文件,whence一定要是SEEK_SET(當前位置可能不以字節偏移量度量),offset只能是0或ftell的返回值。
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);
- 不同之處是這裏的函數類型是
off_t
,它的長度大於32位
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
- 上述函數是ISO C引入的,爲了程序的可移植性應該使用它們
11、格式化IO
11.1 格式化輸出
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
- sprintf可能造成緩衝區溢出
- 一個轉換說明有4個可選擇的部分:
- %[flags][fldwidth][precision][lenmodifier]convtype
- flags
- `:將整數按千分位分組字符
- - :在字段內左對齊輸出
- +:顯示帶符號轉換的正負號
- (空格):字符第一個不是正負號則加空格
- #:指定轉換形式,如十六進制加0x前綴
- 0:添加前導0進行填充
- fldwidth說明最小字段寬度,非負十進制數或*號
- precision說明整數轉換後最少輸出數字位數、浮點數轉換後的最少位數、字符串轉換後的最大字節數精度是一個點,其後跟隨一個非負十進制數或*
- lenmodifier說明參數長度,可以是hh,h,l,ll,j,z,t,L
- convtype可以是d,i,o,u,x,X,f,F,e,E,g,G,a,A,c,s,p,n,C,S,%
- flags
11.2 格式化輸入
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict stream, const char *restrict format, ...);
int sscanf(const char *restrict s, const char *restrict format, ...);
int vfscanf(FILE *restrict stream, const char *restrict format,va_list arg);
int vscanf(const char *restrict format, va_list arg);
int vsscanf(const char *restrict s, const char *restrict format,va_list arg);
12、實現細節
每個標準IO庫都要調用前面討論的IO例程,每個標準IO流都有一個相關聯的文件描述符
int fileno(FILE *stream);
13、臨時文件
char *tmpnam(char *ptr);
FILE *tmpfile(void);
char *mkdtemp(char *template);
int mkstemp(char *template);
- tmpnam產生一個與現有文件名不同的一個有效路徑名字串,每次調用產生一個不同的路徑名,最多調用TMP_MAX次。
- tmpfile創建一個臨時的二進制文件(wb+),關閉該文件或程序結束時自動刪除。
- mkdtemp創建一個目錄,該目錄有唯一的名字
- mkstemp創建一個文件,該文件有唯一的名字,名字通過template進行選擇,template的後6位設置成設置爲xxxxxx,函數將用不同字符來替換這些佔位符
- mkstemp創建的臨時文件不會自動刪除,需要手動解除鏈接
14、內存流
通過調用setbuf()或setvbuf()讓IO庫使用我們自己的緩衝區。而標準IO流雖然仍使用FILE指針進行訪問,但其實沒有底層文件,所有IO流都是通過在緩衝區與主存之間來回傳遞字節來完成。
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode);
- 函數允許調用者提供緩衝區用於內存流,buff爲空時,自動分配size字節並在流關閉時自動釋放。
- 內存流不適合二進制數據
- 如果buf是null指針,讀寫操作沒有意義
- 增加緩衝流中的數據量以及調用fclose()、fflush()、fseek()、fseeko()、fsetpos()都會在當前位置寫入一個null字節。
#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
- open_memstream()函數創建的流是面向字節的,後者是面向寬字節的
- 與fmemopen區別在於
- 創建的流只用寫打開
- 不能指定自己的緩衝區,但可以通過bufp和sizep訪問緩衝區地址和大小
- 關閉流後不需要自行釋放緩衝區
- 對流添加字節會增加緩衝區大小
- 緩衝區可以增長,下次調用fclose()時可能已經改變
- 因爲避免了內存溢出,內存流非常適合創建字符串
15、標準IO的替代軟件
- 標準IO庫效率不高與需要複製的數據量有關,當使用每次一行函數fgets()、fputs()時,需要複製兩次數據:內核與標準IO緩衝(read、write)、IO緩衝與用戶程序之間