1. 程式人生 > >標準I/O庫(五)

標準I/O庫(五)

前言

UNIX一切皆檔案,掌握檔案的操作尤其重要。以下的內容主要是apue上面第五章內容,介紹了標準I/O庫的一些內容。

流和FILE物件

標準I/O庫,他們的操作都是圍繞流進行的
字元分為單位元組和多位元組,流的定向決定了是所讀、寫的字元是單位元組還是多位元組的。當一個流被建立時,並沒有定向。使用多位元組I/O函式,流的定向就被設定為寬定向,單位元組同理。只有兩個函式可以改變流的定向,freopen和fwide

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
//若流是寬定向的,返回正值;若流是單位元組定向的返回負值,流未定向,返回0

mode引數的說明
* mode為負,fwide將使指定的流是單位元組定向的
* mode為正,fwide將使指定的流是寬定向的
* mode為0,不設定流的定向,但返回標識該流定向的值

緩衝

標準I/O庫提供緩衝的目的是儘可能減少read和write的呼叫。
標準I/O提供了3種緩衝
1. 全緩衝。只有填滿標準I/O緩衝區後才進行實際的I/O操作。對於駐留在磁碟上的檔案通常是由標準I/O庫實施全緩衝的。在一個流上執行第一次I/O操作是,相關I/O函式通常呼叫malloc獲得需要的緩衝。
2. 行緩衝。當在輸入輸出中遇到換行符時,標準I/O庫執行I/O操作。這允許我們一次輸出一個字元(fputc),但只有在寫了一行之後才進行實際的I/O操作。當涉及一個終端時(如標準輸入和標準輸出),通常使用行緩衝。
3. 不帶緩衝。標準I/O庫不對字元進行緩衝儲存。例如,若用標準I/O函式fputs寫15個字元到不帶緩衝的流中,我們就期望這15個字元能立即輸出,就很有可能使用write函式將這些字元寫到相關聯的開啟檔案中。

標準錯誤流stderr通常是不帶緩衝的。
ISO C要求下列緩衝特徵
* 當且僅當標準輸入和標準輸出並不指向互動裝置時,他們才是全緩衝的
* 標準錯誤絕不是全緩衝的

很多系統預設使用下列型別的緩衝
* 標準錯誤是不帶緩衝的
* 若是指向終端裝置的流,則是行緩衝的;否則是全緩衝的

如果不喜歡系統預設的可使用下列兩個函式中的一個更改緩衝型別

#include <stdiio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict
buf, int mode, size_t size);

在任何時候都可以強制沖洗一個流

#include <stdio.h>
int fflush(FILE *fp);
//成功返回0,失敗返回EOF

開啟流

下列3個函式開啟一個標準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 fd, const char *type);
//成功返回檔案指標,失敗返回NULL

這3個函式區別如下
1. fopen函式開啟路徑名為pathname的一個指定的檔案
2. freopen函式在一個指定的流上開啟一個指定的檔案,如若該流已經開啟,則先關閉該流。若流已經定向,則使用freopen清除定向。此函式一般用於將一個指定的檔案開啟為一個預定義的流:標準輸入、標準輸出或標準錯誤
3. fdopen函式取一個已有的檔案描述符(我們可能從open、dup、dup2、fcntl、pipe、socket、socketpair或accept函式得到此檔案描述符),並使一個標準I/O流與該描述符相結合。此函式常用於由建立管道和網路通訊通道函式返回的描述符。因為這些特殊型別檔案不能使用標準I/O函式fopen開啟,所以我們必須先呼叫裝置專用函式以獲得一個檔案描述符,然後用fdopen使一個標準I/O流與該描述符相結合。

fdopn是 POSIX.1 具有的

type引數說明如下

type 說明 open(2)標誌
r或rb 為讀而開啟 O_RDONLY
w或wb 把檔案截斷至0長,或為寫而建立 O_WRONLY
a或ab 追加;為在檔案尾而寫開啟,或為寫而建立 O_WRONLY
r+或r+b或rb+ 為讀和寫開啟 O_RDWR
w+或w+b或w+ 把檔案截斷為0長,或為讀和寫而開啟 O_RDWR
a+或a+b或ab+ 為在檔案尾讀和寫而開啟或建立 O_RDWR

讀和寫流

一旦打開了流,則可在3種不同型別的非格式化I/O中進行選擇,對其進行讀、寫操作
1. 每次一個字元的I/O。一次讀或寫一個字元,如果流是帶緩衝的,則標準I/O函式處理所有緩衝。
2. 每次一行的I/O。如果想要一次讀或寫一行,則使用fgets和fputs。每行都以一個換行符終止。當呼叫fgets時,應說明能處理的最大行長。
3. 直接I/O。fread和fwrite函式支援這種型別的I/O。每次I/O操作讀或寫某種數量物件,而每個物件具有指定的長度。這兩個函式常用於從二進位制檔案中每次讀或寫一個結構。

1. 輸入函式

以下三個函式可用於一次讀一個字元

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
//成功返回下一個字元,失敗或到達檔案末尾,返回EOF

函式getchar()等同於 getc(stdin)。getc可被實現為巨集,fgetc不能。意味著以下幾點
1. getc的引數不應當是具有副作用的表示式,因此它可能會被計算多次
2. 因為fgetc一定是一個函式,所以它可以得到地址。這就允許將fgetc的地址作為一個引數傳遞給另一個函式
3. 呼叫fgetc所需時間很可能比呼叫getc要長,因為呼叫函式所需時間通常長於呼叫巨集

不管是出錯還是到達檔案尾端,這3個函式都返回同樣的值。為了區分這兩種不同的情況,必須呼叫ferror或feof。

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
//兩個函式的返回值,若成功返回非0(真);否則返回0(假)
void clearerr(FILE *fp);

在大多數實現中,為每個流在FILE物件中維護了兩個標誌:
* 出錯標誌
* 檔案結束標誌

呼叫clearerr可以清除這兩個標誌。
從流中讀取資料以後,可以呼叫ungetc將字元再壓送回流中。

#include <stdiio.h>
int ungetc(int c,FILE *fp);
//成功返回c,出錯返回EOF

2. 輸出函式

對應上面每個輸入函式都有一個輸出函式

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
//成功返回c,出錯返回EOF

每次一行I/O

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

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
//成功返回buf;若已到達檔案尾端或出錯,返回NULL

推薦使用fgets
每個輸入都有對應的輸出

#include <stdio.h>
int fputs(const char* restrict str, FILE *restrict fp);
int puts(const char *str);
//成功返回非負值,失敗返回EOF

二進位制I/O

系統提供了以下函式執行二進位制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);
//兩個函式的返回值:讀或寫的物件數

定位流

有3種方式定位標準I/O流
1. ftell和fseek函式。這倆函式都是假定檔案的位置可以存放在一個長整數中
2. ftellofseeko函式。Single UNIX Specification引入了這兩個函式,使檔案偏移量可以不必一定使用長整形。他們使用off_t資料型別替代了長整形
3. fgetpos和fsetpos函式。這兩個函式是由ISO C引入的。他們使用一個抽象資料型別fpos_t記錄檔案位置。這種資料型別可以根據需要定義一個足夠大的數,用以記錄檔案位置。

需要移植到非UNIX系統上需要使用fgetpos和fsetpos函式

格式化I/O

格式化輸出是由5個printf函式處理

#include <stdio.h>
int printf(const char* restrict format, ...);
int fprintf(FIFE *restrict fp, const char* restrict format, ...);
int dprintf(int fd, 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寫至指定的流,dprintf寫至指定的檔案描述符,sprintf將格式化的字元送入陣列buf中。sprintf在該陣列的尾端自動加一個null位元組,但該字元不包括在返回值中。

格式化輸入

#incude <stdio.h>
int scanf(const char* restrict format, ...);
int fscanf(FIFE *restrict fp, const char* restrict format, ...);
int sscanf(const char* restrict buf, const char* restrict format, ...);
//3個函式的返回值,賦值的輸入項數,若出錯或任一轉換前已到達檔案尾端,返回EOF

臨時檔案

ISO C標準I/O庫提供了兩個函式以幫助建立臨時檔案

#include <stdiio.h>
char *tmpnam(char *ptr);
FILE *tmpfile(void);
//成功返回檔案指標,出錯返回null

tmpnam函式產生一個與現有檔名不同的一個有效路徑名字串。每次呼叫它時,都產生一個不同的路徑名,最多呼叫次數是TMP_MAX(定義在