APUE學習筆記:第五章 標準I/O庫

5.1 引言html

標準I/O庫處理不少細節,例如緩衝區分配,以優化長度執行I/O等。這些處理沒必要擔憂如何使用正確的塊長度。這使得它便於用戶使用,可是若是不較深刻地瞭解I/O庫函數的操做,也會帶來一些問題數組

 

5.2 流和FILE對象安全

對於ASCII字符集,一個字符用一個字節表示。對於國際字符集,一個字符可用多個字節表示。標準I/O文件流可用於單字節或多字節字符集。網絡

流的定向決定了所讀、寫的字符是單字節仍是多字節的。當一個流最初被建立時,它並無定向。如若在未定向的流上使用一個多字節I/O函數,則將該流的定向設置爲寬定向的。若在未定向的流上使用一個單字節I/O函數,則將該流的定向設置爲字節定向的。ide

 

只有兩個函數可改變流的定向。freopen函數清除一個流的定向;fwide函數設置流的定向函數

#include<stdo.h>
#include<wchar.h>

int fwide(FILE *fp,int mode);
    
      返回值:若流是寬定向的則返回正值,若流是字節定向的則返回負值,未定向則返回0

根據mode參數的不一樣值,fwide函數執行不一樣的工做:優化

-如若mode參數值爲負,fwide將試圖使指定的流是字節定向的ui

-如若mode參數值爲正,fwide將試圖使指定的流是寬定向的編碼

-如若mode參數值爲0,fwide將不試圖設置流的定向,但返回標識該流定向的值spa

(fwide並不改變已定向流的定向,且fwide無出錯返回。若想知道是否出錯,則可調用fwide前先清楚errno,從fwide返回時檢查errno的值)

 

5.3 標準輸入,標準輸出和標準出錯

對一個進程預約義了三個流,而且這三個流能夠自動地被進程使用,它們是:標準輸入,標準輸出,標準錯誤

這三個標準I/O流經過預約義文件指針stdin、stdout和stderr加以引用。這三個文件指針一樣定義在頭文件<stdio.h>中

 

5.4 緩衝

標準I/O庫提供緩衝的目的是儘量減小使用read和write調用的次數。它也對每一個I/O流自動地進行緩衝管理,從而避免了應用程序須要考慮這一點所帶來的麻煩。

標準I/O提供了三種類型的緩衝:

(1)全緩衝。這種狀況下,在填滿標準I/O緩衝區後才進行實際I/O操做。在一個流上執行第一次I/O操做時,相關標準I/O函數一般調用malloc()得到緩衝區

    術語沖洗(flush)說明標準I/O緩衝區的寫操做。緩衝區可由標準I/O例程自動沖洗(例如當填滿一個緩衝區時),或者能夠調用函數fflush沖洗一個流。

   (在UNIX環境中,flush有兩種意思。在標準I/O庫方面,flush(沖洗)意味着將緩衝區中的內容寫到磁盤上。在終端驅動程序方面,flush(刷清)表示丟棄已存儲在緩衝區中    的數據)

(2)行緩衝。在這種狀況下,當在輸入和輸出中遇到換行符時,標準I/O庫執行I/O操做

    對於行緩衝有兩個限制。第一,當緩衝區滿即便沒有換行符也進行I/O操做。第二,任什麼時候候只要經過標準I/O庫要求從(a)一個不帶緩衝的流,或者(b)一個行緩衝的流(它要求從內核獲得數據),那麼就會形成沖洗全部行緩衝輸出流。在(b)中括號中的說明,其理由是,所需的數據可能已在該緩衝區中,它並不要求在數據須要時才從內核讀數據。很明顯,從不帶緩衝的一個流中進行輸入(a)要求當時從內核獲得數據

(3)不帶緩衝。標準I/O庫部隊字符進行緩衝存儲。例如,若是用標準I/O函數fputs寫15個字符到不帶緩衝的流中,則該函數極可能用write系統調用函數將這些字符當即寫至相關  聯的打開文件上標準出錯流stderr一般是不帶緩衝的,這就使得出錯信息能夠儘快顯示出來,而無論它們是否含有一個換行符)

 

setbuf和setvbuf能夠更改一個流的緩衝類型

#include<stdio.h>

void setbuf(FILE *restrict fp,char *restrict buf);

int setvbuf(FILE *restrict,char *restrict buf,int mode,size_t size);

                        返回值:若成功則返回0,若出錯則返回非0值

這些函數必定要在流已被打開後調用(由於每一個函數都要求一個有效的文件指針做爲它的第一個參數),並且也應該在對流執行任何一個其餘操做以前調用

 

可使用setbuf函數打開或關閉緩衝機制。爲了帶緩衝進行I/O,參數buf必須指向一個長度爲BUFSIZ的緩衝區(定義在<stdio.h>中)。一般在此以後該流就是全緩衝的,可是若是該流與一個終端設備相關,那麼某些系統也可將其設置爲行緩衝。爲了關閉緩衝,將buf設置爲NULL

使用setvbuf,咱們能夠精確地指定所需的緩衝類型。這是用mode參數實現的:

            _IOFBF  全緩衝

            _IOLBF  行緩衝

            _IONBF     不帶緩衝

 

任什麼時候候,咱們均可以強制沖洗一個流

#include<stdio.h>

int fflush(FILE *fp);

                返回值:若成功則返回0,若出錯則返回EOF

此函數使該流全部未寫的數據都被傳送至內核。做爲一個特例,如若fp是NULL,則此函數將致使全部輸出流被沖洗

 

5.5 打開流

下列函數打開一個標準I/O流

#include<stdio.h>

FILE *fopen(const char *restrict pathname,const char *restrict type);   

FILE *freopen(const char *restrict pathname,const char *restrict type,
                    FILE *restrict fp);

FILE *fdopen(int filedes,const char *type);
    
            三個函數返回值:若成功則返回文件指針,若出錯則返回NULL

 

(1)fopen打開一個指定的文件。

(2)freopen在一個指定的流上打開一個指定的文件,若該流已經打開,則先關閉該流。若該流已經定向,則freopen清除該定向。此函數通常用於將一個指定的文件打開爲一個預約義的流:標準輸入,標準輸出或標準出錯

(3)fdopen獲取一個現有的文件描述符,並使一個標準的I/O流與該描述符相結合。此函數經常使用於由建立管道和網絡通訊函數返回的描述符。由於這些特殊類型的文件不能用標準     I/O   fopen函數打開,因此咱們必須先調用設備專用函數以獲取一個文件描述符,而後fdopen使一個標準I/O流與該描述符相關聯

(參數type指定讀寫方式:r或rb(讀,文件必須已存在)、w或wb(刪除以前的內容,寫)、a或ab(在文件末尾寫)、r+或r+b或rb+(讀 寫,文件必須已存在)、w+或w+b或wb+(刪除以前的內容,讀寫)、a+或a+b或ab+(讀寫,只能在末尾寫))

 

調用fclose關閉一個打開的流

#include<stdio.h>

int fclose(FILE *fp);

                            返回值:若成功則返回0,若出錯則返回EOF

在該文件被關閉以前,沖洗緩衝區中的輸出數據。丟棄緩衝區中的任何輸入數據。若是標準I/O庫已經爲該流自動分配了一個緩衝區,則釋放此緩衝區

 

5.6 讀和寫流

一旦打開了流,則可在三種不一樣類型的非格式化I/O中進行選擇,對其進行讀,寫操做:

(1)每次一個字符的I/O。

(2)每次一行的I/O

(3)直接I/O

 

1.輸入函數

如下三個函數可用於一次讀一個字符

#include<stdio.h>

int getc(FILE *fp);

int fgetc(FILE *fp);

int getchar(void);

        //三個函數的返回值:若成功則返回下一個字符,若已達到文件結尾或出錯則返回EOF

函數getchar等價於getc(stdin);

前兩個函數的區別是getc可被實現爲宏,而fgetc則不能實現爲宏

注意,不論是出錯仍是到達文件尾端,這三個函數都返回一樣的值。爲了區分這兩種不一樣的狀況,必須調用ferror或feof

#include<stdio.h>

int ferror(FILE *fp);

int feof(FILE *fp);

                  //兩個函數返回值:若條件爲真則返回非0值,否者返回0
void clearerr(FILE *fp);

在大多數實現中,每一個流在FILE對象中維持了兩個標誌:出錯標誌和文件結束標誌(調用clearerr則清楚這兩個標誌)

 

從流中讀取數據後,能夠調用ungetc將字符再壓回流中

#include<stdio.h>

int ungetc(int c,FILE *fp);

                    //返回值:若成功則返回c,若出錯則返回EOF

壓送回流中的字符之後又可從流中獨處,但讀出字符的順序與壓送回的順序相反。(回送的字符沒必要必定是上一次讀到的字符。不能回送EOF。可是當已經到達文件尾端時,仍能夠回送一字符。下次讀將返回該字符,再次讀則返回EOF。之因此這樣所的緣由是一次成功的ungetc調用會清除該流文件的結束標誌)

 

2.輸出函數

對應於上面所述的每一個輸入函數都有一個輸出函數

#include<stdio.h>

int putc(int c ,FILE *fp);

int fputc(int c,FILE *fp);

int putchar(int c);


                三個函數返回值:若成功則返回c,若出錯則返回EOF

 

 

5.7 每次一行I/O

下面兩個函數提供每次輸入一行的功能

#include<stdio.h>

char *fgets(char *restrict buf ,int n,FILE *restrict fp);

char *gets(char *buf);


            //兩個函數返回值:若成功則返回buf,若已達到文件結尾或出錯則返回NULL

gets是一個不推薦使用的函數。其問題是調用這在使用gets時不能指定緩衝區的長度。這樣就可能形成緩衝區溢出,寫到緩衝區以後的存儲空間中,從而產生不可預料的後果

 

fputs和puts提供每次輸出一行的功能

#include<stdio.h>

int fputs(const char *restrict str,FILE *restrict fp);

int puts(const char *str);


                  //兩個函數返回值:若成功則返回非負值,若出錯則返回EOF

函數fputs將一個以null符終止的字符串寫到指定的流,尾端的終止符null不寫出(這並不必定是每次輸出一行)

puts將一個以null符終止的字符串寫到標準輸出,終止符不寫出。可是,puts而後又將一個換行符寫到標準輸出。

 

5.8 標準I/O的效率

實例:5_1 用getc和putc將標準輸入複製到標準輸出

 1 #include"apue.h"
 2 int main()
 3 {
 4     int c;
 5     while((c=getc(stdin))!=EOF)
 6         if(putc(c,stdout)==EOF)
 7             err_sys("output error");
 8     if(ferror(stdin))
 9         err_sys("input error");
10     exit(0);
11 }

 

實例:5_2 用fgets和fputs將標準輸入複製到標準輸出

 1 #include"apue.h"
 2 
 3 int main()
 4 {
 5     char buf[MAXLINE];
 6     while(fgets(buf,MAXLINE,stdin)!=NULL)
 7         if(fputs(buf,stdout)==EOF)
 8         err_sys("output error");
 9     if(ferror(stdin))
10         err_sys("input error");
11     exit(0);
12 }

 

同read、write相比可知,系統調用與普通的函數調用相比一般須要花費更多的時間

 

5.9 二進制I/O

若是輸入數據中包含有null字節或換行符,則fgets也不能正確工做。所以,提供了下列兩個函數以執行二進制I/O操做

#include<stdio.h>

size_t fread(void *restrict ptr,size_t size,size_t nobj,FILE *restrict fp);

size_t fwrite(const void *restrict ptr,size_t size,size_t nobj,FILE *restrict fp);

                //兩個函數的返回值:讀或寫的對象數

這些函數有兩種常見用法:

(1)讀或寫一個二進制數組

float data[10];
if(fwrite(&data[2],sizeof(float),4,fp)!=4)
    err_sys("fwrite error");

(2)讀或寫一個結構

struct{
        short count;
        long total;
        char name[NAMESIZE];
}item;
if(fwrite(&item,sizeof(item),1,fp)!=1)
        err_sys("fwrite error");


5.10 定位流

有三種方法定位標準I/O流:

(1)ftell和fseek函數(他們都假定文件的位置能夠存放在一個長整形中)

(2)ftello和fseeko函數。(可使文件偏移量沒必要必定使用長整形。它們使用off_t數據類型代替了長整形)

(3)fgetpos和fsetpos函數。(使用一個抽象數據類型fpos_t記錄文件的位置。這種數據類型能夠定義爲記錄一個文件位置所需的長度

 

須要移植到非UNIX系統上運行的應用程序應當使用fgetpos和fsetpos.

#include<stdio.h>

long ftell(FILE *fp);
                        //返回值:若成功則返回當前文件位置指示,若出錯則返回-1

in fseek(FILE *fp,long offset,int whence);
                        //返回值:若成功則返回0,若出錯則返回非0值

void rewind(FILE *fp);

off_t ftello(FILE *fp);
                            //返回值:若成功則返回當前文件位置指示,若出錯則返回-1

int fseeko(FILE *fp,off_t offset,int whence);
                            //返回值:若成功則返回0,若出錯則返回非0值

int fgetpos(FILE *restrict fp,fpos_t *restrict pos);

int fsetpos(FILE *fp,const fpos_t *pos);
                            //兩個函數返回值:若成功則返回0,若出錯則返回非0值

whence的值:SEEK_SET,SEEK_CUR,SEEK_END

fgetpos將文件位置指示器的當前值存入由pos指向的對象中。在之後調用fsetpos時,可使用此值將流從新定位至該位置。

 

5.11 格式化I/O

1.格式化輸出

執行格式化輸出處理的是4個printf函數

#include<stdio.h>

int printf(const char *restrict format);

int fprintf(FILE *restrict fp,const char *restrict format,...);
                    //兩個函數返回值:若成功則返回輸出字符數,若輸出出錯則返回負值
int sprintf(char *restrict buf,const char *restrict format,...);

int snprintf(char *restrict buf,size_t n,const char *restrict format,...);
                   //兩個函數返回值:若成功則返回存入數組的字符數,若編碼出錯則返回負值

printf將格式化數據寫到標準輸出,fprintf寫至指定的流,sprintf將格式化的字符送入數組buf中。sprintf在該數組的尾端自動加一個null字節,但該字節不包括在返回值中。snprintf比sprintf安全,由於其不會發生溢出(詳見http://www.ahathinking.com/archives/202.html)

 

2.格式化輸入

執行格式化輸入處理的是三個scanf函數

#include<stdio.h>

int scanf(const char *restrict format, ...);

int fcanf(FILE *restrict fp,const char *restrict format, ...);

int sscanf(const char *restrict buf, const char *restrict format, ...);

 //三個函數返回值:指定的輸入項數;若輸入出錯或在任意變換前已到達文件結尾則返回EOF

scanf族用於分析輸入字符串,並將字符序列轉換成指定類型的變量。

 

5.12  實現細節

每一個標準I/O流都有一個與其相關聯的文件描述符,能夠對一個流調用fileno函數以得到其描述符

#include<stdio.h>
int fileno(FILE *fp);

                //返回值:與該流相關聯的文件描述符

若是要調用dup或fcntl等函數,則須要此函數

 

實例:5_3 對各個標準I/O流打印緩衝狀態信息

 1 #include"apue.h"
 2 void pr_stdio(const char *,FILE *);
 3 
 4 int main()
 5 {
 6     FILE *fp;
 7     fputs("enter any character\n",stdout);
 8     if(getchar()==EOF)
 9         err_sys("getchar error");
10     fputs("one line to standard error\n",stderr);
11     
12     pr_stdio("stdin",stdin);
13     pr_stdio("stdout",stdout);
14     pr_stdio("stderr",stderr);
15     
16     if((fp=fopen("/etc/host.conf","r"))==NULL)
17         err_sys("fopen error");
18     if(getc(fp)==EOF)
19         err_sys("getc error");
20     pr_stdio("/etc/host.conf",fp);
21     exit(0);
22 }
23 void pr_stdio(const char *name,FILE *fp)
24 {
25     printf("stream = %s, ",name);
26     /*
27      *The following is nonportable
28      */
29     if(fp->_IO_file_flags&_IO_UNBUFFERED)
30         printf("unbuffered");
31     else if(fp->_IO_file_flags&_IO_LINE_BUF)
32         printf("line buffered");
33     else
34         printf("fully buffered");
35     printf(", buffer size = %d\n",fp->_IO_buf_end - fp->_IO_buf_base);
36 }

從運行結果看出,該系統的默認狀況是:當標準輸入,輸出連至終端時,它們是行緩衝的。行緩衝的長度是1024字節。(這並無將輸入,輸出的行長限制爲1024字節,這只是緩衝區的長度)。當將這兩個流從新定向到普通文件時,它們就變成是全緩衝的,起緩衝長度是該文件系統優先選用的I/O長度

 

5.13 臨時文件

ISO C標準I/O庫提供了兩個函數以幫助建立臨時文件

#include<stdio.h>

char *tmpnam(char *ptr);
                    //返回值:指向唯一路徑名的指針
FILE *tmpfile(void);
                //返回值:若成功則返回文件指針,若出錯則返回NULL

tmpnam函數產生一個與現有文件名不一樣的一個有效路徑名字符串。每次調用它時,它都產生一個不一樣的路徑名,最多調用次數是TMP_MAX。TMP_MAX定義在<stdio.h>中

tmpfile建立一個臨時二進制文件(類型wb+),在關閉該文件或程序結束時將自動刪除這種文件

實例:5_4 tmpnam和tmpfile函數實例

 1 #include"apue.h"
 2 int main()
 3 {
 4     char name[L_tmpnam],line[MAXLINE];
 5     FILE *fp;
 6     printf("%s\n",tmpnam(NULL)); //first temp name
 7     tmpnam(name);        //second temp name
 8     printf("%s\n",name);
 9     if((fp=tmpfile())==NULL)        //creat temp file
10     err_sys("tmpfile error");
11     fputs("one line of output\n",fp);    //write to temp file
12     rewind(fp);            //then read it back
13     if(fgets(line,sizeof(line),fp)==NULL)
14         err_sys("fgets error");
15     fputs(line,stdout);        //print the line we wrote
16     exit(0);
17 } 

tmpfile函數常用的UNIX技術是先調用tmpnam產生一個唯一的路徑名,而後用該路徑名建立一個文件,並當即unlink它。(

 

SUS爲處理臨時文件定義了另外兩個函數,他們是XSI的擴展部分,其中第一個是tempnam函數

#include<stdio.h>

char *tempnam(const char *directory,const char *prefix);

                    //返回值:指向惟一路徑名的指針

tempnam是tmpnam的一個遍體,它容許調用這爲所產生的路徑名指定目錄和前綴。

對於目錄有4中不一樣的選着,按下列順序判斷其條件是否爲真,而且使用第一個爲真的做爲目錄:

(1)若是定義了環境變量TMPDIR,則用其做爲目錄

(2)若是參數directory非NULL,則用其做爲目錄

(3)將<stdio.h>中的字符串P_tmpdir用做目錄

(4)將本地目錄(一般是/tmp)用做目錄

 

實例:5_5 演示tempnam函數

 1 #include"apue.h"
 2 
 3 int main(int argc,char *argv[])
 4 {
 5     if(argc!=3)
 6     err_quit("usage:a.out <directory><prefix>");
 7     
 8     printf("%s\n",tempnam(argv[1][0]!=' ' ? argv[1]:NULL,
 9         argv[2][0]!=' ' ? argv[2]:NULL));
10     exit(0);
11 }

 

XSI定義的第二個函數是mkstemp。它相似於tmpfile,可是該函數返回的不是文件指針,而是臨時文件的打開文件描述符

#include<stdlib.h>

int mkstemp(char *template);

            //返回值:若成功則返回文件描述符,若出錯則返回-1

它所返回的文件描述符可用於讀、寫該文件。臨時文件名是用template字符串參數選擇的。

與tempfile不一樣的是,mkstemp建立的臨時文件不會自動被刪除。若想從文件系統名字空間中刪除該文件,則咱們須要自行unlink它

使用tmpnam和temnam的一個不足之處是:在返回惟一路徑名和應用程序用該路徑名建立文件之間有一個時間窗口。在該時間窗口期間,另外一個進程可能建立一個同名文件。tempfile和mkstemp函數則不會產生這種問題

 

5.14 標準I/O代替軟件

標準I/O庫並不完善

標準I/O庫的一個不足之處是效率不搞,這與它須要複製的數據量有關。當使用每次一行函數fgets和fputs時,一般須要複製兩次數據:一次是在內核和標準I/O緩衝之間,第二次是在標準I/O緩衝和用戶程序中的行緩衝區之間。快速I/O庫避免了這一點,其方法是讀一行的函數返回指向該行的指針,而不是將該行復制到另外一個緩衝區中。

標準I/O庫另外一種代替版本:sifo。這一軟件包速度上與fio相近,一般快於標準I/O庫

許多標準I/O庫實現可用於C函數庫中,這種C函數庫是爲內存較小的系統(如嵌入式)設計的。

相關文章
相關標籤/搜索