2016-02-01
標(biāo)準(zhǔn)io
標(biāo)準(zhǔn)io處理了很多細(xì)節(jié)谷婆,例如緩存分配辽聊,優(yōu)化長度執(zhí)行io等。
流和file對象
之前我們了解所有的io函數(shù)都是針對于文件描述符的身隐,而對于標(biāo)準(zhǔn)io贾铝,他們的操作是圍繞流進(jìn)行的。當(dāng)用表針io庫打開或者創(chuàng)建一個文件時垢揩,我們已經(jīng)使得一個流與一個文件相結(jié)合。
當(dāng)打開一個流時斑匪,標(biāo)準(zhǔn)io函數(shù)fopen返回一個指向FILE對象的指針锋勺。該對象是一個結(jié)構(gòu)它包含了io庫為管理該流所需要的所有信息:文件描述符狡蝶,指向流緩存的指針贪惹,緩存的長度寂嘉,當(dāng)前在緩存中的字符數(shù),出錯標(biāo)志等等
標(biāo)準(zhǔn)輸入硼端、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)出錯
對于一個進(jìn)程預(yù)定義了三個流他們自動的可為進(jìn)程使用:標(biāo)準(zhǔn)輸入寓搬、標(biāo)準(zhǔn)輸出、和標(biāo)準(zhǔn)出錯曼尊。這三個標(biāo)準(zhǔn)io流同于預(yù)定義文件指針stdin,stdout,stderr加以引用脏嚷。這三個文件指針同樣定義在頭文件<stdio.h>中
緩存
標(biāo)準(zhǔn)io提供緩存的目的是盡可能減少使用read和write調(diào)用的數(shù)量瞒御。它也對每個io流自動地今次那個緩存管理,避免了應(yīng)用程序需要考慮這一點帶來的麻煩趾唱。它提供三種類型的緩存
- 全緩存蜻懦。這種情況下,當(dāng)填滿標(biāo)準(zhǔn)io緩存后才進(jìn)行實際io操作悠咱。對于駐在磁盤上的文件通常是由標(biāo)準(zhǔn)io庫實施全緩存征炼。在一個流上執(zhí)行第一次io操作時,相關(guān)標(biāo)準(zhǔn)io函數(shù)通常調(diào)用malloc獲取使用的緩存眼坏。緩存可由標(biāo)準(zhǔn)io例程自動執(zhí)行刷新(例如當(dāng)填滿一個緩存)或者可以調(diào)用fflush刷新一個流酸些。
- 行緩存檐蚜。這種情況下囤屹,當(dāng)輸入和輸出中遇到新行標(biāo)準(zhǔn)(或者緩存區(qū)寫滿)io庫執(zhí)行io操作。這允許我們一次輸出一個字符乡括,但只有在寫了一行后才進(jìn)行實際的io操作智厌。當(dāng)流設(shè)計一個終端時,典型地使用行緩存敷扫。
任何時候只要通過標(biāo)準(zhǔn)輸入輸出庫要求從一個不帶緩存的流或者一個行緩存流得到數(shù)據(jù)诚卸,那么就會造成刷新所有行緩存流
- 不帶緩存。標(biāo)準(zhǔn)io庫不對字符進(jìn)行緩存卒密。如果用標(biāo)準(zhǔn)io函數(shù)寫若干字符到不帶緩存的流中棠赛,則相當(dāng)于用write系統(tǒng)調(diào)用函數(shù)將這些字符寫至相關(guān)聯(lián)的打開文件上睛约。標(biāo)準(zhǔn)出錯流不帶緩存,使得錯誤信息盡快顯示出來辩涝。ANSIC要求:當(dāng)且僅當(dāng)標(biāo)準(zhǔn)輸入和輸出并不涉及交互作用設(shè)備時,他們才是全緩存的捉邢。標(biāo)準(zhǔn)出錯時全緩存的沧踏。
對于任何一個給定的流,如果我們并不喜歡這些系統(tǒng)默認(rèn)秘案,則可以調(diào)用下列函數(shù)更改緩存類型
#include <stdio.h>
void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size) mode _IOFBF 全緩存 _IOLBF 行緩存 _IONBF不帶緩存
這些函數(shù)一定要在流已被打開后調(diào)用,而且也應(yīng)在對流執(zhí)行任何一個其他操作之前調(diào)用
如果指定一個不帶緩存的流則忽略buf和size赚导。如果指定全緩存或者行緩存赤惊,則buf和size可以可選地指定一個緩存及其長度。
一般而言我們應(yīng)由系統(tǒng)選擇緩存長度圈暗,并自動分配緩存裕膀。這樣處理是,標(biāo)準(zhǔn)io庫在關(guān)閉此流時將自動釋放此緩存寸齐。
任何時候抄谐,我們可以強(qiáng)制刷新一個流蛹含。
#include<stdio.h>
int fflush(FILE *fp)
此函數(shù)使該流所有未寫的數(shù)據(jù)都被傳遞至內(nèi)核.fp 為NULL刷新所有輸出流。
打開流
FILE *fopen(const char *pathname, const char *type)
FILE *freopen(const char *pathname, const char *type, FILE *fp)
FILE *fdopen(int filedes, const char *type)
freopen在一個特定的流上打開一個指定的文件挣惰,如果該流已經(jīng)打開則先關(guān)閉憎茂。此函數(shù)一般用于講一個指定的文件大開衛(wèi)一個預(yù)定義的流锤岸。
fdopen 去一個現(xiàn)存的文件描述符,并使一個標(biāo)準(zhǔn)io流與該描述符相結(jié)合拳氢。此函數(shù)通常用于由創(chuàng)建管道和網(wǎng)絡(luò)通信函數(shù)獲得的描述符蛋铆。因為這些特殊類型的文件不能用標(biāo)準(zhǔn)io fopen打開必須先調(diào)用設(shè)備專用函數(shù)獲得一個文件描述符,然后調(diào)用fdopen使一個標(biāo)準(zhǔn)io與該描述符結(jié)合留特。type指定io流的讀寫方式。
對于fdopen ,type參數(shù)中因為描述符已經(jīng)打開苟蹈,所以fdopen為寫打開并不截短文件右核。另外標(biāo)準(zhǔn)io添加方式也不能用于創(chuàng)建文件。當(dāng)添加類型打開一個文件后贺喝,則每次都將數(shù)據(jù)寫到文件的當(dāng)前結(jié)尾躏鱼,如若有多個進(jìn)程用都采用這種模式打開同一文件,那么來自每個進(jìn)程的數(shù)據(jù)都將正確寫到文件中扳抽。
當(dāng)以讀寫類型打開一個文件時殖侵,如果中間沒有fflush,fseek fsetpos 或者rewind則輸入后面不能直接輸出,且輸出后面不能直接輸入
調(diào)用fclose關(guān)閉一個打開的流
在該文件關(guān)閉之前楞陷,刷新緩存中的輸出數(shù)據(jù)茉唉,緩存中的輸入數(shù)據(jù)被丟棄,如果是自動分配的緩存艾凯,則釋放此緩存懂傀。
讀寫流
一旦打開了流,則可以在三種不同類型的非格式化io中進(jìn)行選擇
- 每次一個字符的io恃泪。一次讀寫一個字符犀斋,如果流帶緩存,則標(biāo)準(zhǔn)io處理所有緩存
- 每次一行的io览效,使用fgets和fputs一次讀寫一行,當(dāng)調(diào)用fgets時應(yīng)說明能處理的he最大行長
- fread 和 fwrite函數(shù)支持這種類型的io禁筏,每次io操作或?qū)懩撤N數(shù)量的對象衡招,而每個對象有指定的長度。
輸入函數(shù)
以下三個函數(shù)可以用于一次讀一個字符
#include<stdio.h>
int getc(FILE *fp)
int fgetc(FILE *fp)
int getchar(void)
函數(shù)getchar等同于getc前兩個函數(shù)的區(qū)別是getc可被實現(xiàn)為宏州刽,而fgetc則不能實現(xiàn)為宏這意味著:
1浪箭、getc參數(shù)不應(yīng)當(dāng)是具有副作用的表達(dá)式
2、因為fgetc一定是一個函數(shù)匹表,所以可以得到其地址宣鄙。這就允許將fgetc的地址作為一個參數(shù)傳遞給另一個函數(shù)
3冻晤、調(diào)用fgetc所需的時間可能長于getc因為調(diào)用函數(shù)通常所需的時間長于調(diào)用宏。檢驗下<stdio.h>頭文件的大多數(shù)實現(xiàn)從中可以見getc是一個宏鼻弧,其編碼就有較高的工作效率攘轩。
這三個函數(shù)以unsigned char 類型轉(zhuǎn)換為int的方式返回下一個字符。說明為不帶符號的理由是鹉胖,如果最高位1也不會使返回值為負(fù)够傍。要求整型返回值的理由是挠铲,這樣就可以返回所有可能的字符再加上一個已發(fā)生錯誤或者已到達(dá)文件結(jié)尾的指示值。由于EOF經(jīng)常為-1.這就意味著不能將這三個函數(shù)的返回值放在一個字符變量中安聘,還要將返回值與EOF進(jìn)行比較。不管出錯還是到達(dá)文件結(jié)尾三個函數(shù)返回同樣值丘喻,為了區(qū)分這兩種情況必須調(diào)用ferror或者feof.clearerr函數(shù)清除這兩個標(biāo)識
從一個流讀之后念颈,可以調(diào)用ungetc將字符再送回流中。雖然ANSI C支持任意數(shù)量字符送回的實現(xiàn)嗡靡,但是它要求任何一種實現(xiàn)都要支持一個字符的回送功能窟感。一次成功的送回會清除EOF標(biāo)識
輸出函數(shù)
int putc(int c , FILE *fp)
int fputc(int c, FILE *fp)
int putchar(int c);
若成功則返回c出錯則為EOF
每次一行io
char *fgets(char *buf, int n, FILE *fp)
char *gets(char *buf)
這兩個函數(shù)都制定了緩存地址讀入的行將送入其中柿祈,gets從標(biāo)準(zhǔn)輸入讀,而fgets從指定流讀取黑滴。對于fgets必須指定緩存的長度n紧索,此函數(shù)一直讀到下一個新行為止,但不超過n-1如果讀不完下次繼續(xù)本行晚缩。
fputs(const char *strr, FILE *fp)
puts(const char *str)
函數(shù)fputs將一個以null符終止的字符串寫到指定流媳危,終止符null補(bǔ)寫出。fputs并不一定產(chǎn)生一個新行鸣皂。puts一定會產(chǎn)生一個新行
二進(jìn)制io
如果為二進(jìn)制io我們更愿意一次讀或者寫整個結(jié)構(gòu)暮蹂。
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp)
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)
eg:讀寫一個二進(jìn)制數(shù)
float data[10]
if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
讀寫一個結(jié)構(gòu)
struct{
short count;
long total;
char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
error_sys("fwrite error");
在不同系統(tǒng)之間交換二進(jìn)制數(shù)據(jù)的實際解決方法是使用較高層的協(xié)議
定位流
有兩種方法定位標(biāo)準(zhǔn)io流
- ftell和fseek它們都假定文件的位置可以存放在一個長整型中仰泻。
- fgetpos和fsetpos。這兩個函數(shù)引進(jìn)了一個新的數(shù)據(jù)類型fpos_t被啼。要移植到非unix系統(tǒng)上的應(yīng)用應(yīng)當(dāng)使用fgetpos和fsetpos
long ftell(FILE *fp)
int fseek(FILE *fp, long offset, int whence) whence SEEK_SET文件起始位置 SEEK_CUR表示從當(dāng)前文件位置,SEEK_END文件結(jié)尾
void rewind(FILE *fp)
對于一個二進(jìn)制文件泡挺,其位置指示器是從文件起始位置開始度量命浴,并以字節(jié)為計量單位。
rewind將一個流設(shè)置到文件起始位置
int fgetpos(FILE *fp, fpos_t *pos)
int fsetpos(FILE *fp, jconst fpos_t *pos) 成功返回0
格式化io
格式化輸出
執(zhí)行格式化輸出處理的是三個printf函數(shù)
int printf(const char *format, ...)
int fprintf(FILE *fp, const char *format, ...)
int sprintf(char *buf, const char *format, ...)
printf將格式化數(shù)據(jù)寫到標(biāo)準(zhǔn)輸出稚新, fprintf寫到指定流, sprintf將格式化的字符送入數(shù)組buf中跪腹,sprintf在改數(shù)組的尾端自動添加一個null
格式化輸入
int scanf(const char *format, ...)
int fscanf(FILE *fp, const char *format)
int sscanf(const char *buf, const char *format, ...)
實現(xiàn)細(xì)節(jié)
每個io流都有一個與其關(guān)聯(lián)的文件描述符褂删,可以對一個流調(diào)用fileno以獲得其描述符
int fileno(FILE *fp)
如果要調(diào)用dup 或 fcntl等函數(shù),則需要此函數(shù)
臨時文件
標(biāo)準(zhǔn)庫提供了兩個函數(shù)以幫助創(chuàng)建臨時文件
char *tmpnam(char *ptr)
FILE *tmpfile(coid)
tmpnam產(chǎn)生一個與現(xiàn)在文件名不同的一個有效路徑名字字符串冲茸,每次調(diào)用都產(chǎn)生一個不同路徑名屯阀。
tmpfile創(chuàng)建一個臨時二進(jìn)制文件,在關(guān)閉該文件或者程序結(jié)束時將自動刪除這種文件
tempnam是tmpnam的一個變體轴术,它允許調(diào)用者為所產(chǎn)生的路徑名指定目錄和前綴难衰。
char *tempnam(const char * directory, const char *prefix)
對于目錄:
- 如果定義了環(huán)境變量TMPDIR則用其作為目錄
- 如果參數(shù)directory非NULL,則用其作為目錄
- 將<stdio.h>中的字符串P_tmpdir用作為目錄
- 將本地目錄,通常是/tmp用作目錄
如果prefix非NULL逗栽,則他應(yīng)該是最多5個字符的字符串,其用作文件名的頭幾個字符
標(biāo)準(zhǔn)io替代軟件
標(biāo)準(zhǔn)io中一個效率不足之處是需要復(fù)制的數(shù)據(jù)量彼宠,當(dāng)使用每次一行函數(shù)fgets和fgets時通常需要重復(fù)復(fù)制兩次數(shù)據(jù):一次是內(nèi)核和標(biāo)準(zhǔn)io緩存之間鳄虱,一個是在標(biāo)準(zhǔn)io緩存和用戶程序中的行緩存中,fio(快速io庫)避免了這一點凭峡,其方法是使杜一航的函數(shù)返回指向改行的指針拙已。Korn和Vo說明了標(biāo)準(zhǔn)io庫的另一種替代版,sfio.這一軟件包速度上與fio相近摧冀,通潮蹲伲快于標(biāo)準(zhǔn)io。sfio提供了一些新的特征:推廣了io流索昂,使其不僅可以代表文件建车,也可代表存儲區(qū),可以編寫處理模塊并以棧的方式將其壓入io流楼镐。