第五章 標準I/O庫
1. Unix系統標準I/O庫是在系統調用函數基礎上構造的。
2. 流和FILE對象:
♥ 流的概念:ANSI C對程序移進或移出字符的操作進行了抽象,將字節流成爲“流”,當程序打開或
寫入一個文件時,此時就已經使得流與文件進行了結合。
(1) 流的類型主要有:文本流,二進制流.
(2) 一個進程已經預定了三個流,即標準輸入,標準輸出和標準出錯。在文件中,分別對應的是STDIO_FILENO,STDOUT_FILENO和STDERR_FILENO.這三種流可以通過預定義FILE指針stdio, stdout和stderr加以引用。這些均定義在<stdio.h>頭文件中!
♥ FILE對象的概念:FILE是一種數據結構,用於訪問一個流,不能與存儲在磁盤上的文件進行混淆。
3. 錯誤報告函數perror: 在I/O函數中,隨時存在錯誤的可能,標準庫函數在一個外部整型變量errno
中保存錯誤代碼之後再將這個信息傳遞給用戶程序,並提示錯誤的準確原因。
void perror(char const *message)
返回值:若message非空,則perror會打印出message中的內容,後面跟一個分號和一個空格,然後打印出一條解釋error當前錯誤代碼的信息。
4. 緩存:
標準I/O庫提供了三種不同的緩存類型:全緩存,行緩存和無緩存。
♥ 全緩存:對於駐留在磁盤上的文件通常是由標準I/O實施全緩存的。
♥ 行緩存:當流涉及到一個終端(標準輸入或者標準輸出)時,典型地使用行緩存。
行緩存存在以下幾個限制:
(1) 因爲標準I/O庫用來收集每一行的緩存的長度是固定的,所以只要填滿了緩存,那麼即使還沒有寫一個新行符,也進行I/O操作.
(2) 任何時候只要通過標準輸入輸出庫要求從一個不帶緩存的流,或一個行緩存的流(它預先要求從內核得到數據)得到輸入數據,那麼就會造成刷新所有行緩存輸出流. 這也正是如printf函數向標準輸出輸出信息時會立即在終端上輸出,而不管是否行緩存是否填滿。
【注意】fflush函數在全緩存和行緩存方面存在差異:對於全緩衝,fflush移位着將緩存中的內容寫入到磁盤上,而在行緩存方面,fflush表示丟棄已經存在緩存中的數據。
(3) 不帶緩存
(4) ANSI C要求下列緩存特徵:
當且僅當標準輸入和標準輸出並不涉及交互作用設備時,它們纔是全緩存的。
標準出錯決不會是全緩存的。
♥ 改變緩存方式:
void setbuf(FILE *fp, char *buf)
int setvbuf(FILE *fp, char * buf, int mode, size_t size)
(1) setbuf可以打開或者關閉緩存機制,當參數buf設置爲空時,則會關閉緩存機制。否則,參數buf必須指向一個長度爲BUFSIZ(1024)的緩存(參數在<stduo.h>定義) ,爲一個流自行制定緩存區可以防止I/O函數庫默認爲它動態分配一個未知的緩衝區。
(2) 流與緩存的關係:當用setbuf函數爲某個流指定了緩存關係後,此時可以看做流是指向緩存空間的,或者可以理解爲它們共享一個內存區域。
(3) setbuf函數運用經典錯誤與解釋:
#include <stdio.h> |
【解釋】源程序在利用setbuf函數將標準輸出流定向於buf數組之後,將會導致一個錯誤。因爲buf是一個內部數組,而buf得到fflush的最後時刻是main函數結束之後,但是此時buf早已釋放掉了,從而導致未能輸出。解決方法就是將buf數組聲明爲靜態數組或者直接放在函數外面。
(1) 對比setbuf函數,setvbuf函數的功能則更加強大,她不僅自行設置緩存區的大小(size),而且還可以直接指定緩存的類型(mode)。
_IOFBF 全緩存
_IOLBF 行緩存
_IONBF 不帶緩存
5. 打開與關閉流操作函數:#include <stdio.h>
FILE *fopen(const char *pathname, const char *type)
FILE *freopen(const char *pathname, char *type, FILE *fp)-----------重定向
FILE *fdopen(int filedes, const char *type)
♥ fopen是一個普通的打開流的函數,若成功,它的返回值爲指向FILE結構的指針;否則返回一個NULL指針。
【注意】在利用fopen函數試圖打開文件時,一定要檢查函數的返回值,因爲若出現錯誤,它會返回一個NULL指針,這會對後面I/O操作造成錯誤。常見type有:
♥ freopen函數則主要是用來實現重定向,類似於shell中的重定向功能。用來實現在一個特定的
流(fp所指)上打開一個指定的文件,該函數一般用於將一個指定的文件打開爲一個預定義的流:
stdin,stdout和stderr.
#include <stdio.h> int main() { int a,b; freopen("in.txt","r",stdin); //輸入重定向,輸入數據將從in.txt文件中讀取 freopen("out.txt","w",stdout); //輸出重定向,輸出數據將保存在out.txt文件中 scanf("%d %d",&a,&b); printf("%d\n",a+b); fclose(stdin);//關閉文件 fclose(stdout);//關閉文件 return 0; }----------------------------------從文件in.txt文件中讀取輸入a,b值,然後輸出到out.txt |
#include <stdio.h> int main(void) { FILE *fp; fp=freopen("in.txt","r",stderr); freopen("error.out","w",stderr); if(NULL==fp) fprintf(stderr,"error!"); else fprintf(stderr,"True!"); fclose(fp); fclose(stderr); return 0; } |
♥ fdopen函數則是通過取得一個現存的文件描述符,來將其與流進行結合。在使用這個函數之前,一定要調用設備專用函數獲得描述符。
♥ 關閉流: int fclose(FILE *fp)
對於輸出流,fclose函數會在文件關閉前刷新緩存區,若成功則返回0,否則返回EOF。
6. 流I/O函數庫:
♥ 當打開一個流後,可以對其採取格式化函數(讀,寫)或者格式化I/O函數(如printf和scanf).
♥ 按照一次操作的字符數量,可以分爲:一次一個字符、一次一行字符或者指定長度的字符(fread)。
♥ 輸入函數:
int getc(FILE *fp) ;
int fgetc(FILE *fp) ;
int getchar(void);
(1) getchar==getc(stdin)
(2) getc與fgetc的區別主要有:getc可被實現爲宏,而fgetc只能實現爲函數,具體表現在:
getc的參數應該是不具有副作用的表達式(這是宏的性質決定的); fgetc因爲是一個函數,可以將其地址傳向其它函數(這是函數的性質);在運行時間方面,getc要少於fgetc(這是函數調用過程決定的)
(3) 返回值問題:若成功,則返回下一個字符;若已經到達文件尾部或者出錯返回EOF。
【注意】函數的返回值均爲整型數據,中間經歷了將無符號類型字符轉換爲整型數據的過程,返回整型的原因是:這樣就可以返回所有可能的字符值再加上一個已發生錯誤或已到達文件尾端的指示值EOF,在庫函數中EOF一般被定義爲-1, 因此二者還可以直接進行比較判斷。
不管是出錯還是到達文件的尾部,返回值均爲EOF,爲了區分這兩種情況,可以調用ferror和feof。
int ferror(FILE *fp)
int feof(FILE *fp)
返回值:若條件爲真,則返回非0值,否則爲0(假)
♥ 撤銷字符(迴流)操作函數
int ungetc(int ch,FILE *fp)
返回值:若成功,返回字符的ASCII值,否則返回EOF。
|
♥ 輸入與輸出函數(以行爲輸入單位)
char*fgets(char *buf, int n,FILE*fp) ;
char *gets(char *buf) ;
返回值:若成功則爲buf,若已處文件尾端或出錯則爲NULL
int *fputs(const char *str,FILE *fp) ;
int puts(const char *str) ;
返回值:若成功則爲非負值,若出錯則爲 EOF
(1) 兩個函數均指明瞭緩存地址,二者區別是gets是從標準輸入讀,而fgets則是從指定的流中讀。
fgets函數中指明瞭行緩存的長度,若所讀的流中的字符數大於buffer長度-1,則將會停止讀取,然後下一次調用fgets時會從下一個字符開始讀取,不管怎樣,在緩衝區的末尾都會有一個NULL添加到存儲數據,使之成爲一個完整的字符串,gets函數因爲沒有指明緩存的長度,所以經常出現溢出的現象而導致不安全因素。
(2) 兩輸出函數將一個以NULL結尾的字符串寫到指定的流,Null不會被寫出。
|
(3) 常見應用舉例:整行整行讀取文本文件:
|
♥ 二進制I/O(指定長度)函數
(1) 二進制是將數據寫到文件中的高效方式,它避免了在數值轉換爲字符串過程中所涉及到的開銷和精度損失。
(2) 二進制讀寫函數:
size_tfread(void *ptr, size_t size, size_t count, FILE *fp)
size_tfwrite(const void *ptr, size_t size, size_t count, FILE *fp)
其中,ptr指向一個保存數據的內存位置的指針,size是緩存區中每個元素的字節數,count是讀或者寫入的元素數,fp則是數據讀取或者寫入的流。函數的返回值爲讀或者寫的對象數。常見用法:
☻ 讀或者寫一個二進制數組:
list charlist[10]={'a','s','d','e','e','e','e','e','e','\0'};
fp=fopen("/tmp/yang1.txt","w");
if(4 != fwrite(list, sizeof(char), 4, fp))
☻ 讀或者寫一個結構(結構體):
struct {
short count;
long total;
char name [NAMSIZE];
} item;
if(fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwriteerror");
☻ 不足:兩個函數不能在異構系統(不在同一個系統)上進行讀寫操作,原因有:首先,在一個結構中,同一成員的位移量可能隨編譯程序和系統的不同而異;用來存儲多字節整數和浮點值的二進制格式在不同的系統結構間也可能不同。
♥ 定位流函數:
longftell(FILE *fp)
返回值:若成功則爲當前文件指示,若出錯爲–1L
intfseek(FILE *fp,long offset,int from)
返回值:若成功則爲0,否則爲非0
void rewind(FILE*fp)
(1) ftell函數用來返回流的當前位置,即下一個讀取或者寫入將要開始的位置距離文件起始位置的偏移量。對比ftell,fseek函數的功能更加強大,它可以通過指定要定位的起始位置(從文件哪個地方開始),以及相應的偏移量。fseek參數說明如下:
(2) rewind函數可以直接將一個流設置到文件的起始位置。
(3) 常見用法:ftell與fseek函數聯合使用可以求出一個文件的長度。
fp=fopen("/tmp/yang.txt","r");
fseek(fp,0L, SEEK_END); -----------------------將fp定位在文件的尾部
len=ftell(fp) --------------------------------可以求出文件的長度
(4) C標準新引進的函數:
intfgetpos(FILE *fp, fpos_t *pos) ;
intfsetpos(FILE *fp, const fpos_t *pos)
fgetpos將文件位置指示器的當前值存入由pos指向的對象中。在以後調用fsetpos時,可以使用
此值將流重新定位至該位置。若成功,則返回0,否則爲非0.
♥ 格式化I/O函數:
intprintf(const char *format, ...)
intfprintf(FILE *fp, const char *format, ...)
返回值:若成功則輸出字符數,若出錯則爲負值
intsprintf(char *buf, const char *format, ...)
返回值:返回存入數組的字符數
(1) printf函數將格式化數據寫到標準輸出,fprintf函數則是將數據寫到指定的流,sprintf將數據寫入數組buf中,它會在數組的尾部自動添加NULL字節。
(2) 若把可變參數表換成args,則可以轉變成:
int vprintf(const char *format, va_list arg);
int vfprintf(FILE *fp, const char *format,va_list arg) ;
返回值:若成功則爲輸出字符數,若輸出出錯則爲負值
int vsprintf(char *buf, const char *format,va_list arg) ;
返回值:返回存入數組的字符數
|
|
♥ 文件描述符函數:返回與該流相關的文件描述符
int fileno(FILE *fp)
♥ 臨時文件函數
char *tmpnam(char*ptr) -----------------------返回指向一個唯一路徑名的指針
FILE*tmpfile(void) ------------------若成功返回文件指針,否則爲NULL