1、流和FILE對象
系統IO都是針對文件描述符,當打開一個文件時,即返回一個文件描述符,而後用該文件描述符來進行下面的操做,而對於標準IO庫,它們的操做則是圍繞流(stream)進行的。
當打開一個流時,標準IO函數fopen返回一個指向FILE對象的指針。該對象一般是一個結構,它包含了IO庫爲管理該流所須要的全部信息:用於實際IO的文件描述符,指向流緩存的指針,緩存的長度,當前在緩存中的字符數,出錯標誌等等。
咱們稱指向FILE對象的指針(類型爲FILE *)爲文件指針。
2、緩存
標準IO提供緩存的目的是儘量減小使用read和write調用的數量。標準IO提供了三種類型的緩存
(1) 全緩存。在這種狀況下,當填滿標準IO緩存後才進行實際IO操做。對於駐在磁盤上的文件一般是由標準IO實施全緩存的。在一個流上執行第一次IO操做時,相關標準IO函數一般調用malloc得到所需的緩存。
術語刷新(flush)說明標準IO緩存的寫操做。緩存可由標準IO例程自動地刷新(例如當填滿一個緩存時),或者能夠調用函數flush刷新一個流。在UNIX環境中刷新有兩種意思。在標準IO庫方面,刷新意味着將緩存中的內容寫入到磁盤上(該緩存能夠只是局部填寫 的)。在終端驅動程序方面,刷新表示丟棄已存在緩存中的數據。
(2) 行緩存。在這種狀況下,當在輸入和輸出中遇到換行符時,標準IO庫執行IO操做。這容許咱們一次輸入一個字符(用標準IO fputc函數),但只有在寫了一行以後才進行實際IO操做。當流涉及一個終端時(例如標準輸入和標準輸出),典型的使用行緩存。
對於行緩存有兩個限制,一是:由於標準IO庫用來收集每一行的緩存長度是固定的,因此只要填滿了緩存,那麼即便尚未寫一個換行符,也進行IO操做。第二是:任什麼時候候只要經過標準輸入輸出庫要求從(a)一個不帶緩存的流(b)一個行緩存的流(它預先要求從內 核獲得數據)獲得輸入數據,那麼就會形成刷新全部行緩存輸出流。在(b)中帶了一個在擴號中的說明的理由是,所需的數據可能已在緩存中,它並不要求內核在須要該數據時才進行操做。很明顯,從不帶緩存的一個流中進行輸入((a)項)要求當時從內核中獲得數 據。
(3) 不帶緩存。標準IO庫不對字符進行緩存。標準出錯流一般不帶緩存,這可使出錯信息儘快的顯示出來。緩存
ASSI C要求如下緩存特徵:
(1) 當且僅當標準輸入和標準輸出不涉及交互做用設備時,他們纔是全緩存的。
(2) 標準出錯毫不是全緩存的。
對於任何一個流若是咱們不喜歡系統默認則能夠經過如下兩個函數來更改緩存類型:網絡
#include <stdio.h> void setbuf(FILE *fp, char *buf); int setvbuf(FILE *fp, char *buf, int mode, size_t size);
這些函數必須在流打開後和對該流進行任何操做以前調用。
可使用setbuf打開或關閉緩存機制。爲了帶緩存進行IO,參數buf必須指向一個長度爲BUFSIZ的緩存(該常數定義在stdio.h中)。一般在此以後該流就是全緩存的,可是該流與終端設備相關,那麼某些系統也可將其設置爲行緩存。爲了關閉緩存,將buf設置爲NULL。
使用setvbuf能夠精確的說明所需的緩存類型。由mode參數指定:函數
_IOFBF 全緩存
_IOLBF 行緩存,
_IONBF 不帶緩存
若是指定了一個不帶緩存的流,則忽略buf和size參數。若是指定全緩存或行緩存,則buf和size能夠選擇地指定一個緩存及長度。若是該流是帶緩存的,而buf是NULL,則標準IO庫將自動的爲該流分配適當長度的緩存,適當長度是指struct stat結構體中的st_blksize所指定的值。若是系統不能爲爲該流決定此值(例如該流涉及一個設備或一個管道),則分配長度爲BUFSIZ的緩存。
下表列出了這兩個函數的動做,以及它們的各個選擇項
spa
若是在一個函數中分配了一個自動變量類的標準IO緩存,則從該函數返回以前必須關閉該流。SVR4將緩存的一部分用於ta本身的管理操做,因此能夠存放在緩存中的實際數據字節數小於size。通常來講,應由系統選擇緩存的長度,並自動分配緩存。在這樣處理時,標準IO庫在關閉此流時將自動關閉釋放緩存。
能夠在任什麼時候候強制刷新一個流:3d
#include <stdio.h>
int fflush(FILE *fp);
此函數使該流全部的數據傳遞到內核,若是fp是NULL,則此函數刷新全部輸出流。指針
3、打開流code
#include <stdio.h> FILE *fopen(const char *pathname, const char *type); FILE *freopen(const char *pathname, const char *type, FILE fp); FILE *fdopen(int fileds, const char *type); 返回值: 成功文件指針,失敗NULL
三個函數的區別:對象
(1)fopen打開路徑爲pathname的文件。
(2)freopen在一個特定流上(由fp指定)打開pathname文件。若是該流已打開則先關閉。此函數通常用於將一個文件打開爲一個預約義的流:標準輸入、標準輸出和標準出錯。
(3)fdopen取一個現存的文件描述符(可能從open\dup\dup2\fcntl\pipe函數獲得此文件描述符)並使一個標準的IO流與該文件描述符結合。此函數經常使用於由建立管道和網絡通訊通道函數得到的插入符。由於這些特殊類型的文件不能用標準IO fopen打開
type參數指定對該IO流的讀、寫方式,ANSI C規定type參數能夠有15種值:blog
使用字符b做爲type的一部分使得標準IO系統能夠區分文本文件或二進制文件。由於UNXI並不對這兩種文件區分因此在UNIX環境下指定b做爲type的一部分實際上並沒有做用。
對於fdopen,type參數的意義有些區別。由於文件描述符已打開,因此fdopen爲寫而打開並不截短該文件。另外,標準IO添加方式也不能用於建立文件,由於若是一個文件描述符引用一個文件則該文件必定已經存在。
當用添加類型打開一個文件後則每次寫都將數據寫到文件當前尾端處。若是有多個進程用標準IO的方式打開同一個文件,則來自每一個進程的數據都將正確的寫到文件中。
當以讀和寫打開一文件時(type中+號),具備以下限制:
若是中間沒有fflush、fseek、fsetpos或rewind,則在輸出的後面不能直接跟隨輸入。
若是中間沒有fseek、fsetpos、或rewind或者一個輸出操做沒有到達文件尾端,則在輸入操做以後不能直接跟隨輸出。
下表是打開一個流的六種不一樣的方式:
進程
在指定w或a類型建立一個新文件時,沒法說明文件的存取許可位,POSIX.1要求以這種方式建立的文件具備如下權限:
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
除非流引用終端設備,不然按系統默認,它被打開時是全緩存。若流引用終端設備則該流是行緩存。
用fclose關閉一個打開的流:
#include <stdio.h> int fclose(FILE *fp); 返回值: 成功0,出錯EOF
在該文件關閉以前,刷新緩存中的輸出數據。緩存中的輸入數據被丟棄。若是標準IO庫已經爲該流自動分配了一個緩存則釋放該緩存。
當一個進程正常終止時(直接調用exit()函數或者從main函數中返回),則全部帶未寫緩存數據的標準IO流都被刷新,全部打開的標準IO流都被關閉。
4、讀和寫流
一旦打開了流,則能夠在如下三種不一樣類型的非格式化IO中進行選擇,對其進行讀寫:
(1) 每次一個字符的IO。一次讀或寫一個字符,若是流是帶緩存的,則標準IO函數處理全部緩存。
(2) 每次一行的IO。使用fgets和fputs一次讀或寫一行。每行都以一個新行符結束。當調用fgets時應說明能處理的最大行長。
(3) 直接IO。 fwrite和fread函數支持這種類型的IO。每次IO操做讀或寫某種數量的對象,每一個對象具備指定的長度。這兩個函數經常使用於從二進制文件中讀或寫一個結構。
直接IO(direct IO)這個術語來自ANSI C標準,有時也被稱爲:二進制IO、一次一個對象IO、面向記錄的IO或面向結構的IO。
1.輸入函數
如下三個函數用於一次讀一個字符:
#include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); 返回值:成功則爲下一個字符,若是已處於文件尾端或出錯則爲EOF
getchar等同於getc(stdin)。前兩個函數的區別是getc可被實現爲宏,而fgetc則不能。(這裏的可被實現爲宏即大多數UNIX系統中getc的實現是這樣:在<stdio.h>中#define getc(FILE *fp) xxx(FILE *fp),即getc不是一個函數而是一個宏)這意味着:
(1) getc的參數不該當是具備反作用的表達式。
(2) 由於fgetc必定是個函數,因此可獲得其地址。這就容許將fgetc的地址做爲一個參數傳送給另外一個函數。
(3) 調用fgetc所需時間可能長於調用getc,由於調用函數一般所需的時間長於調用宏。
這三個函數以unsigned char類型轉換爲int的方式返回下一個字符。返回int的緣由是函數能夠返回一個負值(指示出錯或已到達文件尾端),在<studio.h>中常數EOF常要求是一個負值,其值常常是-1。因此不能將這三個函數的返回值放到一個字符變量中。
不論是出錯仍是到達文件尾端,這三個函數都返回一樣的值。爲了區分這兩種不一樣的狀況,須調用ferror或feof。
#include <stdio.h> int ferror(FILE *fp); int feof(FILE *fp); 返回值:條件爲真返回非0值,不然0 void clearerr(FILE *fp);
在大多數實現的FILE對象中,爲每一個流保持了兩個標誌
從一個流讀以後,能夠調用ungetc將字符再送回到流中。
#include <stdio.h> int ungetc(int c, FILE *fp);
送回到流中的字符又能夠從流中讀出,但讀出字符的順序與送回的順序相反。回送的字符不必定是上一次讀到的字符。EOF不能回送。可是當已到達文件尾端時仍能夠回送一個字符。下次讀將返回該字符,再次讀則返回EOF。之因此能夠這樣作的緣由是一次成功的ungetc調用會清除該流的文件結束指示。
當正在讀一個輸入流,並進行某種形式的分字或分記號操做時,會常常用到回送字符操做。有時須要先看一看下一個字符以決定如何處理當前字符。而後就須要方便的將剛查看的字符送回,以便下一次調用getc時返回該字符。
2. 輸出函數
#include <stdio.h> int putc(int c, FILE *fp); int fputc(int c, FILE *fp); int putchar(int c); 返回值:成功返回c,出錯EOF
putchar等同於putc(c, stdout)
5、 每次一行IO
下面兩個函數提供每次輸入一行的功能:
#include <stdio.h> char *fgets(char *buf, int n, FILE *fp); char *fgets(char *buf);
gets從標準輸入讀。對於fgets必須指定緩存buf的長度n。此函數會一直讀到下一個新行符爲止,可是不超過n-1個字符,讀入的字符被送入到緩存。該緩存以null字符結尾。若是該行包括最後一個新行符的字符數超過n-1,則只返回一個不完整的行,並且緩存老是以null字符結尾。對fgets的下一次調用會繼續該行。
gets函數不被推薦使用由於不能指定緩存的長度,gets並不將新行符存入緩存中。
#include <stdio.h> int fputs(const char *str, FILE *fp); int puts(const char *str);
返回值:成功返回非負值,出錯EOF
函數fputs將一個以null符終止的字符串寫入到指定的流,終止符null不寫出。這並不必定是每次輸出一行,由於它並不要求在null符以前必定是新行符,可是一般在null符以前是一個新行符。
puts將一個以null符終止的字符串寫入到標準輸出,終止符不寫出。可是puts而後將一個新行符寫到標準輸出。