三、標準I/O函數庫
C標準庫提供了文件的標準 I/O函數庫,相比前述的系統調用,主要差別是實現了跨平臺的用戶態緩衝的解決方案。標準I/O庫使用簡單,與系統調用I/O相似,也包括打開、讀寫、關閉這些操作。
系統調用是操作系統直接提供的函數接口。因爲運行系統調用時,Linux必須從用戶態切換到內核態,執行相應的請求,然後再返回到用戶態,所以應該儘量減少系統調用的次數,從而提高程序的效率。
1、文件指針和流
系統級的I/O操作函數都是針對文件描述符的。即打開文件時返回一個文件描述符,然後可以直接對該文件描述符進行操作。
對於標準I/O操作函數來說,打開或創建一個文件時,會返回一個指向FILE對象的指針。該FILE對象通常是一個結構體,它包含了I/O函數庫爲管理該FILE對象所需要的儘可能多的信息。包括用於實際I/O文件的文件描述符,指向流緩存的指針,緩存長度等。
文件指針與文件描述符是一一對應的關係,這種對應關係由標準I/O庫自己內部維護。應用程序調用時,只需要提供文件指針即可。文件指針指向的數據類型爲FILE型,但應用程序無須關係它的具體內容。
在標準I/O中,一個打開的文件稱爲流(stream),流可以用於讀(輸入流)、寫(輸出流)或者是讀寫(輸入輸出流)。每個進程在啓動後就會打開三個流,與打開的三個文件相對應:stdin代表標準輸入流,stdout代表標準輸出流,stderr代表標準錯誤輸出流(與地層文件描述符0,1和2相對應),它們都是(FILE*)型的指針。標準錯誤輸出流不進行緩衝,輸出的內容會馬上同步到文件(控制檯設備)。
2、標準 I/O編程
2.1、打開和關閉流
fopen函數用於打開一個文件流,其原型如下:
◆FILE *fopen(const char *filename, const char *mode);
filename:被打開的文件的名稱(可包含路徑)。
mode:字符串,用於表示打開的模式。
返回值:打開成功後的文件指針,失敗則返回NULL。
mode如下:(此參數是一個字符串,而不是一個字符,所以應該使用雙引號)
字符串 含義
“r” 或 “rb” 以只讀方式打開
“w” 或 “wb” 以只寫方式打開,若文件有內容,則清空
“a” 或 “ab” 以只寫方式打開,原內容保留,寫入的內容附加在文件流尾部
“r+” 或 “rb+ "或 "r+b" 以更新方式打開,此時文件可讀可寫
“w+” 或 wb+ 或"w+b " 以更新方式打開,文件可讀可寫,但打開時清空文件內容
“a+” 或 "ab+ "或 "a+b " 以更新方式打開,文件可讀可寫,寫入的內容附加在文件流尾部
字母b表示文件是一個二進制文件,而不是一個文本文件。
fclose 函數用於關閉文件,其原型如下:
◆int fclose(FILE *stream);
這個函數可以改變 stream參數所代表的文件,如果改變成功則返回 0,否則將返回 EOF 並且設置變量errno 的值以指示錯誤。
改變前會自動將文件流中的數據寫入文件。
2.2、讀和寫流
一旦打開了流,則可以在三種不同類型的非格式化I/O中進行選擇,對其進行讀寫操作:
(1).每次一個字符的I/O
(2).每次一行的I/O
(3).數據塊I/O,數據塊I/O操作讀或寫一定數量的對象,而每個對象具有指定的長度。fread, fwrite函數常用於每次讀或寫一個結構。
2.3、每次一個字符的I/O
字符輸出函數:
◆int fgetc(FILE *stream);
◆int getc(FILE *stream);
◆int getchar(void);
fgetc 函數用於從文件流中讀取一個字符,與它功能相同的函數時 getc,其中 stream 參數是要讀取的文件流。它們返回值雖然是整型,但實際表示的是讀到的字符,只不過進行了類型轉換。如果讀操作發送錯誤或者到達文件尾,則返回值是 EOF。
getc 與 fgetc 的區別在於它可能是由宏定義實現的,因此參數可能在宏展開以後被使用多長,如果參數本身是一個表達式就會被多長求值,這種情況在使用中應該避免。
getchar 函數用於從標準輸入流讀取一個字符,實際上對 getchar的調用完成等價於getc(stdin),它從標準輸入裏讀取下一個字符。
字符輸入函數:
◆int putc(int c, FILE * stream) ;
◆int fputc(int c, FILE * stream) ;
◆int putchar(int c) ;
fputc 函數用於向文件流寫入一個字符,其原型如下:putc函數與它的功能相同:
c:是要寫入的字符,它雖然是整型,但寫入時會將其轉換爲無符號字符型。
stream:要寫入的文件指針。
返回值:寫入的字符轉換成整型後的值,發送錯誤則返回EOF。
putc 函數與fputc函數的區別在於它有可能是用宏定義實現的。
putchar 用於向標準輸出寫入一個字符,與putc等同:
putc(c,stdout),把單個字符寫到標準輸出。
2.4、每次一行的I/O
行輸出函數:
◆char * gets(char *s)
◆char fgets(char * s, int size, FILE * stream)
fgets 用於從文件流中讀取一行數據:
s:指向一個緩衝區,用於存放讀到的數據。
size:讀取的字節數上限,實際讀取的字節數不會超過 size-1。
stream:要讀取的文件指針。
返回值:等於 s,如果有錯誤發生或文件結束,則返回 NULL。
用 fgets函數讀取數據時,當讀到一個換行符,或者文件結束,或者讀取的字節數達到 size-1,則讀取操作不再繼續,函數返回。fgets 函數還會在讀到的數據最後加一個字符 \0,使之變成一個合法的字符串。注意,如果讀到換行符,則換行符也在讀到數據中。
gets 函數用於從標準輸入讀取一行數據,參數 s指向用於存放數據的緩衝區,如果讀取成功則返回值就是s,否則返回 NULL。
gets 是一個不提倡使用的函數,因爲它對讀入的字節數沒有控制,緩衝區是否會溢出完全取決於用戶的輸入。
行輸入函數:
◆ int fputs(const char *str, FILE *fp)
◆int puts(const char *str)
fputs 函數用於向文件流寫入一個字符串,其原型如下:
s:要寫入的字符串,必須是以 \0結尾的合法字符串。
stream:要寫入的文件指針。
返回值:非負數表示寫入成功,有錯誤發生則返回 EOF。
fputs 函數在向文件流寫入字符串時,字符串的結束符 \0並不會被寫入。
puts 函數將字符串寫入標準輸出, 其中 s參數是要寫入的字符串,它的返回值的含義與 fputs 函數相同。
與 fputs 函數不同的是,puts 函數在將字符串寫入之後會再寫入一個換行符。
2.5、數據塊I/O
◆size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)
fread函數用於從打開的文件流中讀數據:
ptr:指向用於存放讀取到的數據的緩衝區。
size:被讀取的數據塊的長度。
nitems:要讀取的數據塊的個數。
stream:被讀取的文件指針。
返回值:實際讀取到的數據塊的個數。
使用 fread函數需要注意的是,它以數據塊(或稱記錄)爲單位進行讀取,返回值也是成功讀取的數據塊的個數,而不是字節數,這個數目有可能比要讀取的個數 nitems 少。
◆size_t fwrite(const void * ptr,size_t size, size_t nmemb, FILE * stream)
fwrite 函數用於向打開的文件流寫入數據:
ptr:指向存放寫入數據的緩衝區。
size:要寫入的數據塊的長度。
nitems:要寫入的數據塊的個數。
stream:要寫入的文件指針。
返回值:實際寫入的數據塊的個數。
與 fread函數類似,fwrite 函數也是數據塊爲單位向文件流寫入數據的。
2.6、格式化輸出
fprintf 是向文件流格式化寫入數據的函數,其原型如下:
◆int fprintf(FILE *stream, const char *format,...);
stream:要寫入的文件指針。
format:格式字符串。
可變參數:要寫入的數據。
返回值:如果寫入成功,則返回格式化後字符串的長度,也就是寫入數據的長度,負數表示有錯誤發生。
常用輸出轉換符:
格式符 功能
%d 或 %i 按有符號十進制格式輸出整型參數
%u 按無符號十進制格式輸出無符號整型參數
%o 按無符號八進制格式輸出無符號整型參數
%x 按無符號十六進制格式輸出無符號整型參數,使用字母 a,b,c,d,e,f
%X 按無符號十六進制方式輸出無符號整型參數,使用字母 A,B,C,D,E,F
%c 將整型參數轉換爲無符號字符型,並輸出爲字符
%f 按十進制格式輸出高精度浮點型參數
%e 按科學計數法格式輸出高精度浮點型參數,使用字母 e
%E 按科學計數法格式輸出高精度浮點型參數,使用字母 E
%g 或 %G 可理解爲系統自帶選擇 %f 或 %e 格式輸出
%p 按十六進制格式輸出指針型參數
%s 將字符指針型參數視爲字符串輸出
因爲格式字符串中的符號 %有了特殊的含義,所以要原樣輸出一個 %,則需要連續寫兩個 %,即 %%。
常用輸出格式符標誌(放在 %的後面):
字符 作用
數字 0 當輸出數字時,填充 0 而不是空格
減號 - 修改爲左對齊方式,空格填充在右邊
空格 對應正數來說,左邊預留一個空格作爲符號位
加號 + 總是在正數左邊加上 + 符號,在負數左邊加上 - 符號
我們常用的 printf 函數實際上是對 fprintf 函數的包裝,它用來向標準輸出寫入格式的字符串,其原型如下:
◆int printf(const char *format, ...);
它比 fprintf函數少一個文件指針參數,因爲這個文件指針一定是 stdout。
與格式化輸出相關的還有一個函數 sprintf,它並不是文件 I/O 操作,而是將格式化的字符串輸出到一個緩衝區中,原型:
◆int sprintf(char *str, const char *format,...);
其中 str參數就指向用於存放結果的緩衝區。sprintf 函數會在輸出字符串的末尾加上結束符 \0。使用這個函數時要注意,str
指向的緩衝區要有足夠的大小來容納生成的字符串,否則就有內存訪問越界的問題。很多情況下並不能事先知道結果字符串的長度,這時可
以用下面這個函數:
◆int snprintf(char *str, size_t size, constchar *format, ...);
size:限制生成字符串的長度,即寫入緩衝區的字節數。如果格式化後的字符串長度等於或大於 size,則只寫入前 size-1個字節,然後寫入結束符 \0。
返回值:格式化後的字符串長度。
2.7、格式化輸入函數
fscanf 可以從文件流以一定的格式讀取數據,其原型如下:
◆int fscanf(FILE *stream, const char *format, ...);
stream:要讀取的文件指針。
format:格式字符串。
可變參數:一般是指針,指向用於存儲到的數據流量。
返回值:成功解析的數據項的個數(不是字節數),失敗則返回 EOF。
格式字符串中的字符將與輸入流中讀到的字符進行匹配,具體來說有以下幾種情況。
空白字符:包括空格、製表、換行等字符,將與輸入流中的連續 0個或多個空白字符相匹配,也就是說,一個空白字符可以消 耗多個空白字符。
普通字符:不想與從輸入流讀入的字符相同。
轉換符:以符合 %開始的多個字符,這時輸入流中讀入的字符將按某種格式解析爲數據,存入對應的可變參數指向的變量中。
常用輸入轉換符:
轉換符 作用
%d 以十進制格式讀入整數,存在整型變量中
%i 當下一個字符是 0時,以八進制格式讀取整數;當下兩個字符是 0x 或 0X 時,以十六進制格式讀入整數;否則以十進制格式讀入整數,存放在整型變量中。
%u 以十進制格式讀入整數,存放在無符號整型變量中
%o 以八進制格式讀入整數,存放在無符號整型變量中
%x 或 %X 以十六進制格式讀入整數,存放在無符號整型變量中
%f,%g,%e 或 %E 讀入浮點數,存放在浮點型變量中
%s 讀入字符串,字符串從下一個非空白字符開始,再遇到一個空白字符或者達到指定的域寬後結束。字符串存放在對應的參數指向的緩衝區中,末尾會自動加上 \0
%c 讀入域寬所指定個數的字符,默認是一個。不跳過開始的空白字符,讀入的字符放在對應參數指向的字符數組中,末尾不加 \0
scanf 函數類似於 fscnaf函數,只不過是從標準輸入讀取數據,原型:
◆int scanf(const char *format, ...);
還有一個 sscanf 函數可以從字符串中格式化讀取數據,原型:
◆ int sscanf(const char *str, const char*format, ...);
其中,str參數就是被讀取的字符串
2.7、定位流
fseek函數的功能是把當前位置設置到offset處,whence參數決定了相對於文件的位置,其原型如下:
◆int fseek(FILE *stream, long offset, int whence);
stream:被操作的文件指針。
offset:讀寫位置的偏移量。
whence:用於指定偏移量的相對啓點。
返回值:0 表示操作成功, -1 表示操作失敗並且設置 errno 變量的值爲錯誤碼。
whence 參數的取值及含義:
SEEK_SET:表示偏移量相對於文件的開頭。
SEEK_CUR:表示偏移量相對於當前的讀寫位置。
SEEK_END:表示偏移量相對於文件末尾。
如果要將讀寫位置移動到文件的開頭,還可以使用這個函數:
◆void rewind(FILE *stream);
ftell 函數可以得到文件流的讀寫位置,其原型:
◆long ftell(FILE *stream);
參數 stream 是文件指針,返回值就是文件流的當前讀寫位置(相對於文件開頭)。
2.8、標準 I/O 錯誤處理:
當標準 I/O 操作發送錯誤時,比如返回 NULL 指針或者 EOF,可以通過讀 errno 變量得到錯誤碼。
更方便的是使用標準 I/O 的錯誤判斷函數,如:
◆int ferror(FILE *stream);
◆int feof(FILE *stream);
ferror函數檢查文件在用各種輸入輸出函數進行讀寫時是否出錯。返回0表示沒有錯,否則有錯。
feof函數判斷文件是否處於文件結束位置,如文件結束返回1,否則返回0。