標準IO函數庫隱藏了buffer大小和分配的細節,使得我們可以不用關心預分配的內存大小是否正確的問題。
雖然這使得這個函數庫很容易用,但是如果我們對函數的原理不熟悉的話,也容易遇到很多問題。
?
1 流和FILE實體(Streams and FILE Objects)前面的章節中,IO集中在文件描述符,每一個打開的文件都對應一個文件描述符,通過文件描述符對文件進行操作。
現在使用了標準IO庫,討論的重點集中在流(streams)。
簡要了解一下流:
只有兩個函數可以修改流的orientation:
fwide函數聲明:
#include <stdio.h>
#include <wchar.h>
int fwide(FILE* fp, int mode);
函數返回值:
mode取值的不同決定函數fwide的不同的行為:
當我們打開一個流,函數fopen返回一個指向FILE對象的指針。FILE對象通常是一個結構體,包含所有控制流所需要的信息,包括:
?
2 緩存(Buffering)緩存(buffering)的作用是為了盡可能少地調用read和write系統調用。
標準IO庫提供三種類型的buffering:
完全緩存(Fully buffered):在這種緩存機制中,實際的IO操作發生在緩存被寫滿時。正在寫入硬盤的文件被完全緩存在buffer中。緩存空間往往在第一次IO操作時通過調用malloc函數獲取;
行緩存(Line buffered):在這種緩存機制中,實際的IO操作發生在新的一行字符被讀入或者輸出時,所以允許每一次只輸出一個字符。行緩存有兩點需要注意:buffer的大小是固定的,所以即使當前行沒有讀入或輸出結束,依然可能發生實際的IO,當buffer被寫滿時;一旦有輸入(從無緩存流或者行緩存流中輸入)發生,所以已在buffer中緩存的輸出流都會被立刻輸出(flush)。
flush:標準IO緩存中內容立刻寫入硬盤或者輸出。在終端設備中,flush的作用也可能是丟棄緩存中得數據。
無緩存(Unbuffered):不緩存輸入或輸出內容。例如,如果我們使用fputs函數輸出15個字符,那么我們希望這15個字符盡可能快地被打印出來。如標準錯誤輸出就要求是無緩存輸出。
ISO C標準要求下面的緩存特性:
上面的標準顯然沒有具體說明各種情況,一般來說:
我們可以使用函數setbuf和setvbuf函數更改流的緩存機制。
函數聲明:
#include <stdio.h>
void setbuf(FILE* restrict fp, char* restrict buf);
int servbuf(FILE *restrict fp, char* restrict buf, int mode, size_t size);
函數返回值:
這些函數必須在流打開之后,其他流操作執行之前被調用。
函數作用:
setbuf可以打開或關閉緩存,打開緩存時,buf指向一個大小為BUFSIZ(stdio.h中定義的宏)的buffer,通常打開的時完全緩存,如果當前流關聯的是終端設備,有的系統也會使用行緩存;
servbuf可以指定打開哪種類型的緩存。mode的參數可以取如下的值,如果指定為無緩存,則參數buf和size都會被忽略。
函數行為總結如下表所示:
通常來說,我們應該讓系統自己選擇buffer大小并自動分配,這樣標準IO庫會在關閉流時自動釋放該內存。
?
flush函數。
函數聲明:
#include <stdio.h>
int fflush(FILE *fp);
函數作用:
使得該流的所有緩存中未寫入硬盤的數據傳入內核中。
一種特殊情況是,如果fp為NULL,fflush會使得所有緩存的數據都被flush。
?
3 打開流(opening a stream)函數fopen、freopen和fdopen函數用來打開一個標準輸入輸出流。
函數聲明:
#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 fd, const char *type);
函數細節:
參數type取值如下表所示,一共有15種取值,有得取值作用相同:
表格說明:
當打開一個流對文件進行讀寫時,有兩個限制:
六種方式打開一個流總結如下表所示:
需要注意的一點是,當以w和a模式創建一個新文件時,并不能像open或create函數一樣指定文件的權限標志位。
一種解決方法是通過調整我們的umask。
打開的流默認的是完全緩存,如果該流關聯的是終端設備,則是行緩存。
像之前提到的那樣,我們打開了一個流,并在其他操作之前,可以調用setbuf或setvbuf函數修改緩存方式。
關閉流
函數聲明:
#include <stdio.h>
int fclose(FILE* fp);
函數細節,關閉流之前:
當我們打開一個流,我們有三種讀寫方式可供選擇:
函數聲明:
#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
函數返回值:
函數細節:
?
函數聲明:
#include <stdio.h>
int ferror(FILE* fp);
int feof(FILE* fp); ? ?// Both return: nonzero(true) if condition is true, 0(false) otherwise
void clearerr(FILE* fp);
在大多的實現中,FILE對象中會維護兩個flag:
這兩個flag都可以通過調用clearerr清空。
?
讀取一個流后,我們可以調用函數ungetc壓回讀出來的字符。
函數聲明:
#include <stdio.h>
int ungetc(int c, FILE* fp);
函數返回值:c if OK, EOF on error
函數細節:只支持單個個字符的壓回。
使用場景:
壓回操作常使用在下面的場景:對于一個輸入流,我們需要根據下一個字符來判斷該如何處理當前的字符。
?
輸出函數輸出函數和我們討論過的輸入函數一一對應,不再贅述。
函數聲明:
#include <stdio.h>
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);
?
5 逐行輸入輸出操作(Line-at-a-Time IO)函數fgets和gets提供了逐行輸入功能。
函數聲明:
#include <stdio.h>
char *fgets(char* restrict buf, int n, FILE* restrict fp);
char *gets(char* buf);
函數細節:
函數fputs和puts提供了逐行輸出的功能。
函數聲明:
#include <stdio.h>
int fputs(const char* restrict str, FILE* restrict fp);
int puts(const char* str);
函數細節:
?
6 標準輸入輸出效率分析比較標準:
將一定量的數據從標準輸入拷貝到標準輸出,計算這一過程所需要的
Code:
使用getc和putc的版本:
#include "apue.h"
?
int
main(void)
{
? ? int ? ? c;
?
? ? while ((c = getc(stdin)) != EOF)
? ? ? ? if (putc(c, stdout) == EOF)
? ? ? ? ? ? err_sys("output error");
?
? ? if (ferror(stdin))
? ? ? ? err_sys("input error");
?
? ? exit(0);
}
使用fgets和fputs的版本:
#include "apue.h"
?
int
main(void)
{
? ? char? ? buf[MAXLINE];
?
? ? while (fgets(buf, MAXLINE, stdin) != NULL)
? ? ? ? if (fputs(buf, stdout) == EOF)
? ? ? ? ? ? err_sys("output error");
?
? ? if (ferror(stdin))
? ? ? ? err_sys("input error");
?
? ? exit(0);
}
測試數據:95.8M 3百萬行
測試結果(和第三章中的數據進行了對比,之前跳過了該章節,可以自行查看一下):
結果說明:
?
?
7 小結標準IO函數庫分為兩篇來介紹,本篇是第一篇,主要介紹了
?
?
參考資料:
《Advanced PRogramming in the UNIX Envinronment 3rd》?
?
新聞熱點
疑難解答