天天看點

Unix 标準I/O總結和與檔案I/O的比較

我們可以将檔案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);