1. 程式人生 > >第五章 標準I/O庫

第五章 標準I/O庫

本章講述的是標準I/O庫的很多細節,如緩衝區的分配、以優化的塊長度執行I/O等,因為有這些處理,讓我們不必擔心如何選擇正確的塊長度。

5.2 流和FILE物件

1.流定向決定了所讀、寫的字元是單位元組還是多位元組。當一個流最初被建立時,它並沒有定向。如果在未定向的流上使用一個多位元組I/O函式,則該流的定向設定為寬定向。如果在未定向的流上使用一個單位元組I/O函式,則該流的定向設定為位元組定向。
函式fwide()設定流的定向
//返回值: 若流是寬定向,返回正值;位元組定向,返回負值,未定向返回0.

 int fwide(FILE *stream, int mode);

引數mode的值
如果mode的引數值為負,函式將使指定位元組定向流;
如果mode的引數值為正,函式將使指定寬定向流;
如果mode的引數值為0,函式將試圖不設定位元組流,但返回標實該流定向的值;

5.4 緩衝

標準的I/O提供了3種類型的緩衝
1、全緩衝:即填滿標準的I/O緩衝區後才進行實際的I/O操作
沖洗(flush):說明標準I/O緩衝區的寫操作。緩衝區可由標準I/O例程自動沖洗
注意:unix環境中沖洗(flush)有兩種意思:在標準I/O庫方面。flush意味著將緩衝區的內容寫到磁碟上,在終端驅動方面,flush(重新整理)表示丟棄已儲存在緩衝區中的資料。
2、行緩衝:當輸入和輸出遇到換行符時,標準I/O庫執行I/O操作。
3、不帶緩衝:標準I/O庫不對字元進行緩衝儲存。標準錯誤流通常情況下是不帶緩衝的。
很多系統使用下列型別的緩衝:
1、標準出錯時不帶緩衝的。
2、若是指向終端裝置的流,則是行緩衝的,否則是全緩衝。
更改緩衝型別:

//函式需在流已被開啟後呼叫,成功返回0,出錯返回非0
void setbuf(FILE * restrict fp,  char * restrict buf  );//開啟或關閉緩衝機制,引數buf必須是指向一個長度為BUFSIZE的緩衝區。為了關閉緩衝將buf設定為NULL。
int setvbuf(FILE  * restrict fp , char   *restrict  buf ,int mode, size_t size );//確切的說明所需的緩衝型別,由mode引數指定,如果指定一個不帶緩衝的流則忽略buf和size引數,如果指定全緩衝或者行緩衝,則buf和size可選擇的指定一個緩衝區及長度。

mode引數的取值:
_IOFBF 全緩衝
_IOLBF 行緩衝
_IONBF 不帶緩衝

5.5開啟流

成功返回檔案指標,若出錯返回NULL
FILE * fopen (const char * restrict pathname, const char * restrict type);//開啟一個路徑名為pathname的檔案

FILE *freopen(const char  * restrict pathname, const char  *restrict type , FILE * restrict fp);//指定的流上開啟一個檔案,一般用於將一個指定的檔案開啟為一個預定義的流:標準輸入、標準輸出、標準出錯。

FILE * fdopen( int fd,  const char * type)//取一個已有的檔案描述符,並使一個標準的I/O流與該描述符相結合。常用於建立管道或網路通訊通道函式返回的描述符。

type引數—指定該I/O流的讀寫方式:r 或rb w或wb r+或rb+ a+或a+b或ab+

除非流引用終端裝置,否則按照系統預設,流開啟時是全緩衝的,若流引用終端裝置,則該流是行緩衝的。

5.6讀和寫流

一旦打開了流,則可以在3種不同型別的非格式話I/O中進行選擇,對其進行讀、寫操作。
1、一次一個字元的I/O。
2、每次一行的I/O 使用fgets和fputs函式。
3、直接I/O,fread和fwrite函式。

1、輸入函式
//3個函式可用於一次讀寫一個字元,成功返回下一個字元,出錯或到達檔案尾端,返回EOF
int getc(FILE *fp);//巨集函式
int fgetc(FILE * fp);
int getchar(void);

但是不管是出錯或者到達檔案尾端,這三個函式都返回同樣的值,為了區分這兩種情況,必須呼叫ferror和feof

//兩個函式的返回值:若為真。返回非0,否則返回假(0);
int ferror(FILE *fp);//出錯標誌
int feof(FILE * fp);//檔案結束標誌

從流中讀取資料後,可以呼叫ungetc將字元再壓送回流中。

//成功返回c,出錯返回EOF
int ungetc(int c ,FILE * fp);
壓送回到流中的字元以後又可以從流中讀出,但讀出字元的順序與壓送回的順序相反。

2.輸出函式

上述的輸入函式都對應一個輸出函式
int putc(int c, FILE * fp);//巨集函式
int fputc(int c , FILE *fp);
int putchar(int c);

5.7 每次一行I/O

下面的兩個函式提供每次輸入一行的功能
char * fgets(char * restrict buf , int n ,FILE * restrict fp);//從指定的流中讀,將換行符存入緩衝區中
char  * gets(char * buf); //從標準輸入中讀,但不推薦使用,由於不能指定緩衝區的長度,容易造成緩衝區溢位,並且不將換行符存入緩衝區中

每次輸出一行

//若成功返回非負值,若出錯,返回EOF
int fputs(const char * restrict str ,FILE * restrict fp);//以null位元組終止的字串寫到指定的流。
int puts(const char *str);//以null位元組終止的字串寫到標準輸出,終止符不寫出。但是puts會在結尾處自動新增一個換行符

5.9二進位制I/O

執行二進位制I/O操作的兩個函式

//兩個函式的返回值,讀或寫的物件數
size_t fread (void * restrict ptr, size_t size, size_t nobj,FILE * restrict fp);
size_t fwrite (void * restrict ptr, size_t size, size_t nobj,FILE * restrict fp);

使用二進位制I/O的基本問題,它只能用於讀在同一系統上已寫的資料,在不同的系統下,這兩個函式就可能不能正常工作因為:
1、在一個結構中,同一成員的偏移量可能隨編譯程式和系統的不同而不同(由於不同的對齊要求)。
2、用來儲存多位元組整數和浮點值的二進位制格式在不同的系統結構間也可能不同。

5.10定位流

下面函式實現定位流:
//假定檔案位置可以存放在一個長整型中
long ftell(FILE *fp);,返回值,成功返回當前檔案位置指示,出錯返回-1;
int fseek(FILE * fp, long offset, int whence);//成功返回0,出錯返回-1,offset指定的位元組偏移量,whence取SEEK_SET SEEK_CUP
SEEK_END

5.11格式化I/O
1格式化輸出

int printf(const char * restrict format, ... );//將格式化資料寫到標準輸出
    //成功返回輸出的字元數;出錯返回負值
int fprintf(FILE * restrict fp , const char * restrict format, ...);//寫到指定的流中                                                                                                      //成功返回輸出的字元數;出錯返回負值

int dprintf(int fd, const char * restrict format , ....);//寫到指定的檔案描述符                                                                                      //成功返回輸出的字元數;出錯返回負值

int sprintf(char * restrict  buf, const char * restrict format, ....);//格式化的字元送入陣列,buf中,且在尾端加\0;                                                                                                               //成功返回存入的字元數,編碼出錯返回負值

int snprintf(char *restrict buf, size_t n, const char * restrict format ,.....);//超過n值得所有字元都會被丟去。                                                                                                                             //成功返回存入的字元數,編碼出錯返回負值

2.格式化輸入

//返回值,賦值的輸入項數,出錯或到達檔案尾端返回EOF
int scanf(const char * restrict format, ....);
int fscanf(FILE * restrict  fp, const char  * restrict format, .....);
int sscanf(const char * restrict  buf, const char * restrict format, ....);

5.12 實現細節

獲取流的描述符
int fileno(FILE * fp);//返回值,與該流相關聯的檔案描述符

5.13臨時檔案

char * tmpnam(char *ptr);//返回指向唯一路徑名的指標,產生一個與現有檔名不同的一個有效路徑名的字串,每次呼叫都產生一個不同的路徑名,若ptr為NULL 則所產生的路徑名存放在靜態區中,後續呼叫tmpnam時會重寫該靜態區,如果不是NULL,則認為它應該是指向長度至少是L_tmpnam個字元的陣列。

FILE *tmpfile(void);//成功返回檔案指標,出錯返回NULL,建立一個臨時二進位制檔案,在關閉該檔案或者程式結束時將自動刪除這種問件。

處理臨時檔案:
兩函式檔名字通過template字串進行選擇的,這個字串的後六位設定為XXXXXX的路徑名,成功則修改這些XXXXXX
char * mkdtemp(char * template);//成功返回指向目錄的指標,出錯返回NULL
//建立一個目錄,該目錄有唯一的一個名字

int mkstemp(char * template)//成功返回檔案描述符,若出錯返回-1;建立一個檔案,該檔案有唯一的一個名字

注意:與tempfile不同,mkstemp建立檔案的臨時檔案並不會自動刪除。如果希望從檔案系統名稱空間中刪除該檔案,必須自己對它解除連結。
使用tmpnam和tmpfile存在一個缺點:在返回唯一的路徑名和用該名字建立檔案之間存在一個時間視窗,在這一個時間視窗中,另一個程序可以用相同的名字建立該檔案。

5.14記憶體流

記憶體流不適合儲存二進位制檔案,因為二進位制在資料尾端之前可能包含很多個NULL;
記憶體流的建立:
第一個函式
//成功返回流指標,出錯返回NULL
FILE *fmemopen(void restrict buf, size_t size, const char *restrict type);//允許呼叫者提供緩衝區用於記憶體流,buf引數指向緩衝區的開始位置,size引數指定緩衝區大小的位元組數,當流關閉時緩衝區會被釋放。type引數控制如何使用流。type型別:r w w+ r+ a+。。。。。。

注意:type與標準I/O流中的引數取值基本一樣,但是也存在一些差別:1、無論何時以追加寫方式開啟內流時,當前檔案位置設為緩衝區中的第一個null位元組。如果緩衝區中不存在null位元組,則當前位置就設為緩衝區結尾的後一個位元組。當流並不是以追加寫的方式開啟時,當前位置設為緩衝區的開始位置。因為追加寫模式通過第一個null位元組確定資料的尾端。
其他兩個函式
FILE * open_memstream(char bufp,size_t size);//建立的流是面向位元組

FILE * open_wmemstream(wchar_t **buf, size_t sizep)//建立的流是面向寬位元組的
避免了緩衝區溢位,記憶體流適用於建立字串。因為記憶體流只訪問主存,不訪問磁碟檔案
思考:主存、記憶體、磁碟檔案?
記憶體:速度快,容量小,斷電後不保留,CPU可以直接訪問。
外存:容量大,能長期保留,CPU不可以直接訪問,比如光碟、U盤。
主存:它可以理解為我們電腦主機上的“硬碟”我們需要長期儲存的東西都存在它上面,因為它屬於ROM斷電後的面儲存的資料不會丟失。