一、流 和 FILE對象
之前在文件I/O中提到的函數,都是圍繞文件描述符的,當打開一個文件時,返回的是一個文件描述符,然後對該文件描述符進行後續的I/O操作。而對於標準I/O庫,他們的操作是圍繞着 流 進行的。當用標準I/O庫打開或創建一個文件時,我們就使一個流與一個文件相關聯。
在標準IO流中預定義了3個文件指針,stdin(標準輸入)、stdout(標準輸出)、stderr(標準錯誤)。這三個文件指針都定義<stdio.h>中。
對於ASCII字符集,一個字符用一個字節來表示。對於國際字符集而言,一個字符可用多個字符集來表示。標準I/O文件流可用於單字節或多字節(俗稱"寬")字符集
流的定向,決定了所讀所寫的字符是單字節的還是多字節的。當一個流剛被創建出來時,是沒有定向的,如果在一個沒有定向的流上使用一個多字節的I/O函數,則將該流設置爲寬定向,具體多寬由使用的多字節IO函數定。有兩個函數可以改變流的定向:
(1) fwide函數
#include<stdio.h>
#include<wchar.h>
int fwide(FILE *fp, int mode);
返回值:若流是寬定向的,返回正值;若流是字節定向的,返回負值;若流是爲定向的,返回0
注意:
fwide並不改變以定向流的定向。還應注意的是,fwide無出錯返回。試想一下如果流是無效的會發生什麼呢?我們唯一依靠的就是fwide前先清除errno,從fwide返回時檢查errno的值。
(2) freopen函數
該函數的解釋,可見 三、打開流
二、緩 衝
1、標準IO庫提供緩衝的目的是,儘可能少的使用read和write調用次數。標準IO的底層實現是運用了read和write,通過緩衝區將要寫入或者讀出的數據先放到緩衝區中,然後達到一定的條件後(比如:緩衝區滿、遇見換行符 等等)去調用一次read或是write。
2、標準的IO提供了三種類型的緩衝
(1)全緩衝:
在填滿標準的IO緩衝區後才進行實際的IO操作。對於駐留在磁盤上的文件通常是有標準IO庫實施全緩衝的。在一個流上執行第一次操作時,相關的標準IO函數通常是調用malloc去獲得所需的緩衝區。
該類型的緩衝區會在 緩衝區填滿時 以及 調用fflush()沖刷流時執行一次寫操作。
(2)行緩衝:
在遇到換行符時,執行一次IO操作。對於行緩衝有兩個限制,
第一,因爲標準IO庫用來收集每一行的緩衝區的長度是固定的,所以只要填滿了緩衝區,那麼即使沒有寫一個換行符也執行一次IO操作。
第二,任何時候只要通過標準IO庫要求從一個不帶緩衝的流,或者是一個行緩衝的流(它從內核請求需要的數據)得到輸入數據,那麼就會沖洗所有的行緩衝輸出流。對於行緩衝的流後面的括號中的說明,着重解釋一下:
從不帶緩衝的流中輸入需要從內核中獲取數據。
從行緩衝的流中得到輸入數據,所需的數據可能還在緩衝區中,不需要從內核中讀數據。
(3)不帶緩衝:
不對字符進行緩衝存儲。比如:stderr
ISO C中要求緩衝有一下幾項特徵:
a、當且僅當標準輸入和標準輸出,並不指向交互式設備時,他們纔是全緩衝。當指向交互設備時,是行緩衝還是無緩衝都視情況而定,很多系統在指向交互設備後,默認標準錯誤無緩衝。
b、標準錯誤永遠不會是全緩衝。
對任意一個給定的流,若不喜歡他的默認緩衝類型,可以通過下列函數去進行修改緩衝類型。
#include<stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
返回值:成功,返回0;失敗,返回,非0
void setbuf(FILE *restrict fp, char *restrict buf);
可以使用 setbuf 去打開或關閉緩衝機制,參數buf不需指向一個長度爲 BUFSIZ 的緩衝區,設置之後該流的緩衝區爲全緩衝。如果要關閉緩衝機制,則將 buf 爲NULL。
#include <stdio.h>
char outbuf[50];
#if 0
int main(void)
{
/* 將outbuf與stdout輸出流相連接 */
setbuf(stdout,outbuf);
/* 向stdout中放入一些字符串 */
puts("This is a test of buffered output.");
puts("This output will go into outbuf");
puts("and won't appear until the buffer");
puts("fills up or we flush the stream.\n");
/* 以下是outbuf中的內容 */
// puts(outbuf);
/*刷新流*/
// fflush(stdout);
return 0;
}
#else
#include<stdio.h>
#include<stdlib.h>
//char buf[6];
int main()
{
// static char buf[6];
setbuf(stdout,malloc(100));
int c;
int i = 0;
while((c=getchar())!=EOF && getchar())
{
putchar(c);
#if 1
printf("hello\n");
i++;
if(i>5)
{
return 0;
}
#endif
}
return 0;
}#endif
void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
參數:stream :指向流的指針 ;
buf : 期望緩衝區的地址;
type : 期望緩衝區的類型:
_IOFBF(全緩衝):當緩衝區爲空時,從流讀入數據。或者當緩衝區滿時,向流寫入數 據。
_IOLBF(行緩衝):每次從流中讀入一行數據或向流中寫入一行數據。
_IONBF(無緩衝):直接從流中讀入數據或直接向流中寫入數據,而沒有緩衝區。
size : 緩衝區內字節的數量。
注意:意思是這個函數應該在打開流後,立即調用,在任何對該流做輸入輸出前
下面是這兩個函數,參數設置之後其得到的緩衝類型的表:
補充說明一個常見C語言關鍵字:
三、打開流
#include<stdio.h>
FILE* fopen(const char *restrict pathname, char *restrict type);
FILE* freopen(const char *restrict pathname, char *restrict type, FILE* restrict fp);
FILE* fdopen(int fd, const char *type);
返回值:成功,返回文件指針;失敗,返回NULL
(1) fopen 打開路徑執行一個文件
(2) freopen 在一個制定的流(三參)上打開一個文件(一參),如果該流打開了,那就先關閉之前的流,然後再打開新的文件。如果該流已定向,則 freopen 清除該定向。通常該函數將一個指定的文件打開爲一個預定義的流,標準輸入、標準輸出、標準錯誤。
(3) fdopen 這個函數比較有用。將一個文件描述符(open,dup,管道、socket等)與一個標準IO中的流相結合。此函數用於創建管道和網絡通信通道函數返回的描述符。因爲這些特殊的文件描述符不能用標準IO函數fopen打開,所以必須使用該函數將其文件描述符與流相結合。
這三個都有一個標準IO流的參數,其解釋如下:
注意1:在 UNIX 環境下,UNIX內核對二進制文件 和 文本文件不做區分,故 b 選項沒有意義。
注意2:還有以下在使用fdopen時的注意事項:
注意3:還有注意的一點就是,用 w 或是 a 創建一個新文件時,無發說明該文件的訪問權限位。文件IO中,open和creat都能做到。如果要用標準IO用權限位集來創建文件時,必須使用umask函數。在這裏需要插入一個文件和目錄的知識點umask函數:
include<sys/stat.h>
mode_t umask(mode_t cmask);
返回值:之前的文件模式創建屏蔽字
注意4: 流引用的是終端設備,該流屬於行緩衝;按系統默認情況下,流被打開是全緩衝。
四、讀寫流
在打開流之後,有三種不同類型的非格式化IO進行讀寫操作:
(1) 每次一個字符的IO。一次的讀或寫一個字符,如果流帶緩衝,則標準IO函數處理所有的緩衝。
(2) 每次一行的IO。讀寫以換行符結尾,主要函數有 fputs 和 fgets .
(3) 直接IO。每次的IO操作讀或寫某種數量的對象,而每個對象具有指定的長度。常用於從二進制文件中每次讀寫一個結構等,主要的函數 fread 和 fwrite。
1、用於一次只讀一個字符的函數(總共三個函數)
include<stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
返回值:成功返回下一個字符;
若文件已到達文件尾端或出錯,返回EOF
函數 getchar 等同於 getc(stdin); getc 可被定義爲宏,而fgetc不能實現爲宏,其解釋引用書中原文:
注意:
在這三個函數中,他們無論 出錯 還是 到文件尾端其返回的都是EOF,爲了區分這兩種情況,引入了兩個函數:
include<stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
返回值:條件爲真,返回非0;條件爲假,返回0
void clearerr(FILE *fp);
他的區分原理是,如下:
從流中讀取的數據,可以通過 ungetc 將字符再壓回去;
#include<stdio.h>
int ungetc(int c,FILE* fp);
返回值:若成功,返回c;若出錯,返回EOF
ungetc 該函數實現並不是在底層文件或是設備上執行的,而是在標準IO的緩衝區中操作的。以下是書中全部的解釋:
以上是介紹了每次單個字符的讀操作,以下是寫操作,也是三個函數
include<stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
返回值:成功返回c;若出錯,返回EOF
putchar(c) 等同於 putc(c, stdout) ;putc可被實現爲宏,而 fputc 不能實現爲宏。
文章引用:setbuf 和setvbuf 簡單介紹