我們可以将檔案I/O視為系統調用,核心要執行I/O操作,這裡涉及到頁緩存(高速緩存區)的概念,檔案I/O執不執行與緩存區有關。
而标準I/O是對系統I/O調用的封裝,标準I/O也有緩存區、行緩存的概念。正是由于這二級的緩存模式。導緻标準I/O的效率很低。
當打開一個流時,标準I/O函數fopen傳回一個指向FILE對象的指針。該對象通常是一個結構,它包含了标準I/O庫為管理該流所需的所有資訊,包括:用于實際I/O的檔案描述符、指向用于該緩沖區的指針、緩沖區的長度、目前在緩沖區的字元數以及出錯标志等。為引用一個流,需将FILE指針作為參數傳遞給每個标準I/O函數。
對于标準輸入、标準輸出和标準出錯,他們的檔案描述符對應STFIN_FILENO、STDOUT_FILENO和STDERR_FILENO。這三個标準I/O流通過預定義stdin、stdout和stderr加以引用。這三個檔案指針以及标準I/O函數都定義在頭檔案<stdio.h>中。
緩沖
标準I/O庫提供緩沖的目的是盡可能減少使用read和write調用次數。提供了三種類型的緩沖:
1) 全緩沖:需在填滿标準I/O緩沖區後才進行實際I/O操作。
2) 行緩沖:當在輸入和輸出中遇到換行符時,标準I/O庫執行I/O操作。
3) 不帶緩沖:标準I/O庫不對字元進行緩沖存儲。
一般而言,标準出錯是不帶緩沖的,打開終端裝置的流是行緩沖的,其他所有流則是全緩沖的。當流是全緩沖,但該緩沖區是局部填寫時,可用fflush函數沖洗。
可調用下面的函數更改緩沖區類型:
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, intmode, size_t size);
任何時候,我們都可以強制沖洗一個流:
#include <stdio.h>
int fflush(FILE *stream);
此函數将使該流所有未寫的資料都被傳送至核心。作為一個特例,如若fp是NULL,則此函數将導緻所有輸出流被沖洗。
打開流
#include <stdio.h>
FILE *fopen(const char *path, const char*mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char*mode, FILE *stream);
這三個函數的差別是:
1) fopen打開一個指定的檔案。
2) fropen在一個指定的留上打開一個指定的檔案,如若該流已經打開,則先關閉該流。如若該流已經定向,則fopen清除該定向。此函數一般用于将一直指定的檔案打開為一個預定義的流:标準輸入、标準輸出或标準錯誤。
3) fdopen擷取一個現有的檔案描述符,并使一個标準的I/O流與該描述符相結合。此函數常用于由建立管道和網絡通信函數傳回的描述符。因為這些特殊類型的檔案不能用标準I/Ofopen函數打開,所有我們必須先調用裝置專用函數以獲得一個檔案描述符,然後用fopen使一個标準I/O與該描述符相關聯。
其中的mode參數可以用是以下15種不同的值:
r或rb: 為讀打開
w或wb: 把檔案截短至0長,或為寫而建立
a或ab: 添加;為在檔案寫打開,或為寫打開
r+或r+b或rb+: 為讀和寫打開
w+或w+b或wb+: 把檔案截短至0,或為讀和寫打開
a+或a+b或ab+: 為在檔案尾端讀和寫而打開或建立
#include <stdio.h>
int fclose(FILE *fp);
在檔案被關閉之前,沖洗緩沖區中的輸出資料。如果标準I/O庫已經為該流自動配置設定了一個緩沖區,則釋放緩沖區。
讀和寫流
一旦打開了流,則可在三種不同類型的非格式化I/O中進行選擇,對其讀、寫操作:
1) 每次一個字元是I/O。一次讀或寫一個字元,如果流是帶緩沖區的,則标準I/O函數會處理所有緩沖。
2) 每次一行的I/O。如果想要一次讀或寫一行,則使用fgets和fputs。每行都以一個換行符終止。當調用fgets時,應說明能處理的最大行長。
3) 直接I/O。fread和fwrite函數支援這種類型的I/O。
每次一個字元I/O
輸入函數:
#include <stdio.h>
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
getchar()等價于getc(stdin)。getc和fgetc差別在于getc可被實作為宏,而fgetc則不能實作為宏。
不管是出錯還是到達檔案尾端,這三個函數都傳回同樣的值。為了區分出錯和到達檔案尾端,必須調用ferror和feof函數。
#include <stdio.h>
int feof(FILE *stream);
int ferror(FILE *stream);
這兩個函數的傳回值:若條件為真則傳回非0值,否則傳回0。
每個流在FILE對象中維持了兩個标志:出錯标志和檔案結束标志
調用clearerr則清除這兩個标志。
void clearerr(FILE *stream);
從流讀取資料後,可以調用ungetc将字元再壓入回流中。
int ungetc(int c, FILE *stream);
壓入回流中的字元以後又可以從流中讀出,但讀出的字元順序與壓送回的順序相反。
對于輸出函數:
#include <stdio.h>
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
與輸入函數一樣putchar(c)等效于putc(c, stdout)。putc可實作為宏。
每次一行I/O
#include <stdio.h>
char *fgets(char *s, int size, FILE*stream);
char *gets(char *s);
fgets從指定的流讀,必須指定緩沖區長度size。此函數一直讀到下一個換行符為止,但是不超過n-1個字元,讀入的字元被送入緩沖區。該緩沖區以null字元結尾。如若改行(包括最後一個換行符)的字元數超過n-1,則fgets隻傳回一個不完整的行,但是緩沖區總是以null字元結尾。對fgets的下一次調用會繼續讀改行。
gets從标準輸入讀。它是一個不推薦的函數,因為不能指定緩沖區長度,可能造成緩沖區溢出,寫到緩沖區之後的存儲空間中,進而産生不可預料的後果。
fputs和puts提供每次輸出一行的功能。
int fputs(const char *s, FILE *stream);
int puts(const char *s);
二進制I/O
#include <stdio.h>
size_t fread(void *ptr, size_t size,size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_tsize, size_t nmemb, FILE *stream);
以上兩個函數可一次讀或寫整個結構。
定位流
有三種方法定位标準I/O流
1) ftell和fseek。這兩個函數要求檔案的位置可以存放到一個長整形中。
2) ftello和fseeko。他們可以使檔案檔案偏移量不一定使用長整形。他們用off_t資料類型代替了長整形。
3) fgetpos和fsetpos。他們使用抽象資料類型fpos_t記錄檔案的位置。這種資料累心可以定義為記錄一個檔案的位置所需的長度。
#include <stdio.h>
int fseek(FILE *stream, long offset, intwhence);
long ftell(FILE *stream);
int fseeko(FILE *stream, off_t offset, intwhence);
off_t ftello(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
格式化I/O
輸出:
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char*format, ...);
int sprintf(char *str, const char *format,...);
int snprintf(char *str, size_t size, constchar *format, ...);
輸入:
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char*format, ...);
int sscanf(const char *str, const char*format, ...);
fileno函數
标準I/O庫最終都要調用I/O系統調用函數。每個标準I/O流都有一個與其相關聯的檔案描述符,可以對一個流調用fileno函數以獲得其描述符。
int fileno(FILE *stream);
臨時檔案
#include <stdio.h>
char *tmpnam(char *s);
FILE *tmpfile(void);
tmpnam函數産生一個與現有檔案名不同的一個有效路徑名字字元串。每次調用它,它都産生一個不同的路徑名,最多調用TMP_MAX次。
tmpfile建立一個臨時二進制檔案。
此外還有兩個類似的函數:
#include <stdio.h>
char *tempnam(const char *dir, const char*pfx);
int mkstemp(char *template);