標準I/O庫

標準I/O庫:

流和FILE對象:
    在文件I/O函數都是圍繞這文件描述符的。當打開一個文件時,即返回一個文件描述符,然後該文件描述符就用於後續的I/O
    操作。而對於標準I/O庫,它們的操作時圍繞流進行的。當用標準I/O庫打開或者創建一個文件時,我們已使一個流與一個文件
    相關聯。
    對於ASCLL字符集,一個字符用一個字節表示。對於國際字符集,一個字符可用多個字節表示。標準I/O文件流可用於單字節
    或者多字節(”寬“)字符集。流的定向決定了所讀、寫的字符是單字節或多字節的。當一個流最初被創建是,它並沒有定向。
    如若在未定向的流上使用一個多字節I/O函數(<wchar.h>),則將該流的定向設置爲寬定向的。若在爲定向的流上使用一個
    單字節I/O函數,則將該流的定向設爲單字節定向的。只有兩個函數可改變流的定向。
    fopen函數清除一個流的定向;fwide函數可用於設置流的定向。
    #include <stdio.h>
    #include <wchar.h>
    int fwide(FILE *fp,int mode);
    根據mode參數的不同值,fwide函數執行不同的工作:
    1、如若mode參數值爲負,fwide將試圖使指定的流是字節定向的
    2、如若mode參數值爲正,fwide將試圖使指定的流是寬定向的
    3、如若mode參數值爲0,fwide將不試圖設置流的定向,但返回標識該流定向的值

    注意,fwide並不改變已定向流的定向。還應注意的是,fwide無出錯返回。如若流是無效的,那麼會發生什麼?
    我們唯一可依靠是,在調用fwide前先清除errno,從fwide返回時檢查errno值。
    當打開一個流時,標準I/O函數fopen返回一個指向FILE對象的指針。該對象通常時一個結構,它包含了標準I/O庫
    爲管理該流需要的所有信息,包括用於實際I/O的文件描述符、指向用於該流緩衝區的指針、緩衝區的長度、當前在
    緩衝區的資附屬以及出錯標誌等。

緩衝:
    標準I/O庫提供緩衝的目的時儘可能減少使用read和write調用的次數。它也對每個I/O流自動地進行緩衝管理,從
    而避免了應用程序需要考慮這一點所帶來的麻煩。遺憾的時,標準I/O庫最令人迷惑的也是它的緩衝。
    標準I/O提供了3種緩衝:
    1、全緩衝。在這種情況下,在填滿標準I/O緩衝區後才進行實際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函數fputc),但只有在寫了一行之後才進行實際I/O操作。當流涉及一個終端時,通常使用行緩衝。
    它有兩個限制:
    1、因爲標準I/O庫用來收集每一行的緩衝區的長度是固定的,所以只要填滿了緩衝區,那麼即使還沒有寫一個換行符,
    也進行I/O操作。

    2、任何時候只要通過標準I/O庫要求從(a)一個不帶緩衝的流,或者(b)一個行緩衝的流(它從內核請求需要的數據)
    得到輸入數據,那麼就會沖洗所有行緩衝輸出的流。在(b)中帶了一個在括號中的說明,其理由是,所需的數據可能已
    在緩衝區中,它並不要求一定從內核讀數據。很明顯,從一個不帶緩衝區的流中輸入(a)需要從內核獲得數據。

    3、不帶緩衝。標準I/O庫不對字符進行緩衝儲存。例如,若用標準I/O函數fputs寫15個字符到不帶緩衝的流中,我們
    就期望這15個字符能立即輸出,很可能使用write函數將這些字符寫道相關聯的打開文件中。

    標準錯誤流stderr通常是不帶緩衝的,這就使得出錯信息可以儘快顯示出來,而不管它們是否含有一個換行符。

    很多系統默認:
    1、標準錯誤是不帶緩衝的
    2、若是指向終端設備的流,是行緩衝的,否則是全緩衝的

    對任何一個給定的流,如果我們並不喜歡這些系統默認,則可調用下列兩個函數中的一個更改緩衝類型:
    #include <stdio.h>
    void setbuf(FILE *restrict fp,char *restrict buf);
    int setvbuf(FILE *restrict fp,char *restrict buf,int mode, size_t size);
    這些函數一定要在流已被打開後調用,而且也應在該流執行任何一個其他操作之前調用。

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

    使用setvbuf,我們可以精確地說明所需的緩衝類型。這是用mode參數實現的:
    _IOFBF  全緩衝
    _IOLBF  行緩衝
    _IONBF  不帶緩衝

    如果指定一個不帶緩衝的流,則忽略buf和size參數。如果指定全緩衝或行緩衝,則buf和size可選擇地指定一個緩衝
    區及其長度。如果該流是帶緩衝的,而buf是NULL,則標準I/O庫將自動地爲該分配適當長度的緩衝區。適當長度指的是
    由常量BUFSIZ所指定的值。

    函數      mode    buf 緩衝區及長度          緩衝類型
    setbuf          非空  長度爲BUFSIZ的用戶緩衝區buf  全緩衝或行緩衝
                NULL    無緩衝區                不帶緩衝區

    setvbuf     _IOFBF  非空  長度爲size的用戶緩衝區buf        全緩衝
                NULL    適合長度的系統緩衝區buf       全緩衝
            _IOLBF  非空  長度爲size的用戶緩衝區buf        行緩衝
                NULL    適合長度的系統緩衝區buf       行緩衝
            _IONBF  忽略  無緩衝區                不帶緩衝區

    如果一個函數內分配一個自動變量類的標準I/O緩衝區,則從該函數返回之前,必須關閉該流。另外,其些實現將緩衝區的
    一部分用於存放它自己的管理操作信息,所以可以存放在緩衝區中的實際數據字節數少於size。一般而言,應由系統選擇
    緩衝區的長度,並自動分配緩衝區。在這種情況下關閉此流時,標準I/O庫將自動釋放緩衝區。
    任何時候,我們都可以強制沖洗一個流:
    #include <stdio.h>
    int fflush(FILE *fp);
    此函數使流所有未寫的數據都被傳送至內核。作爲一種特殊情形,如若fp是NULL,則此函數將導致所有輸出流被沖洗。

打開流:
    下列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);
    3個函數的返回值:若成功,返回文件指針,若出錯,返回NULL。

    3個函數的區別:
    1、fopen函數打開路徑名爲pathname的一個指定的文件
    2、freopen函數在一個指定的流上打開一個指定的文件,如若該流已經打開,則先關閉該流。若該流已經定向,則使用
    freopen清除該定向。此函數一般用於將一個指定的文件打開爲一個預定義的流:標準輸入、標準輸出、標準錯誤。
    3、fdopen函數取一個已有的文件描述符(我們可能從open、dup、dup2、fcntl、pipe、socket、socketpair或
    accept函數得到此文件描述符),並是一個標準的I/O流與該描述符相結合。此函數用於由創建管道和網絡通信函數返回
    的描述符。因爲這些特殊型的文件後用fdopen使一個標準I/O流與該描述符相結合。

    type參數指定對該I/O流的讀、寫方式:
    type        說明                  open標誌
    r或rb        爲讀而打開               O_RDONLY
    w或wb        把文件截斷至0長,或爲寫而創建     O_WRONLY | O_CREAT | O_TRUNC
    a或ab        追加;爲在文件尾寫而打開,或爲寫而創建 O_WRONLY | O_CREAT | O_APPEND
    r++或r+b或rb+ 爲讀和寫而打開             O_RDWR  
    w++或w+b或wb+ 把文件截斷至0長,或爲讀和寫而打開       O_RDWR | O_CREAT | O_TRUNC
    a++或a+b或ab+ 爲在文件尾讀和寫而打開或創建      O_RDWR | O_CREAT | O_APPEND

    使用字符b作爲type的一部分,這使得標準I/O系統可以區分文本文件和二進制文件。因爲UNIX內核並不對這兩種文件
    進行區分,所以在UINX系統環境下指定字符b作爲type的一部分實際上並無作用。
    對於fdopen,type參數的意義有點區別。因爲該描述符已被打開,所以fdopen爲寫而打開並不截斷該文件。例如,若
    該描述符原來是由open函數創建的,而且該文件已經存在,則其O_TRUNC標誌將決定是否截斷該文件。fdopen函數不能
    截斷它爲寫而打開的任一文件。另外,標準I/O追加寫方式也不能用於創建文件(因爲如果一個描述符引用一個文件,則
    該文件一定已經存在)。

    注意爲了正確處理追加寫方式,該文件必須用O_APPEND標誌打開。
    當以讀和寫類型打開一個文件時(type中+號),具有下列限制:
    1、如果中間沒有fflush、fseek、fsetpos或者rewind,則在輸出的後面不能直接跟隨輸入
    2、如果中間沒有gseek、fsetpos或rewind,或者一個輸入操作沒有到達文件尾端,則在輸入操作之後不能直接
    跟隨輸出。

    注意,在指定w或者a類型創建一個新文件時,我們無法說明該文件的訪問權限位(open函數和creat函數則能做到)
    POSIX.1要求使用如下的權限位集來創建文件:
    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
    我們也可以使用umask值來限制這些權限。
    除非流引用終端設備,否則按系統默認,流被打開時是全緩衝的。若流引用終端設備,則該流是行緩衝的。一旦打開了
    流,那麼在對該流執行任何操作之前,如果希望,則可使用前節所說的setbuf和setvbuf改變緩衝區的類型。
    調用fclose關閉一個打開的流:
    #include <stdio.h>
    int fclose(FILE *fp);
    在該文件被關閉之前,沖洗緩衝區中的輸出數據。緩衝區的任何輸入數據被丟棄。如果標準I/O庫已經爲該流自動分配了
    一個緩衝區,則釋放此緩衝區。
    當一個進程正常終止時(直接調用exit函數,或從main函數返回),則所有帶未寫緩衝數據的標準I/O流都被沖洗,所有
    打開的標準I/O流都被關閉。

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

1、輸入函數
    以下3個函數可用於一次讀一個字符:
    #include <stdio.h>
    int getc(FILE *fp);
    int fgetc(FILE *fp);
    int getchar(void);
    函數getchar等同於getc(stdin)。前兩個函數的區別是,getc可被實現爲宏,而fgetc不能實現宏。
    1、getc的參數不應當是具有副作用的表達式,因爲它可能會被計算多次
    2、因爲fgetc一定是個函數,所以可以得到其地址。這就允許將fgetc的地址作爲一個參數傳送給另外一個函數。
    3、調用fgetc所需時間很可能比調用getc要長,因爲調用函數所需的時間通常長於調用宏。

    判斷出錯還是到達文件尾端了:
    #include <stdio.h>
    int ferror(FILE *fp);
    int feof(FILE *fp);
    void clearerr(FILE *fp);
    在大多數實現中,爲每個流在FILE對象中維護了兩個標誌:
    1、出錯標誌
    2、文件結束標誌
    調用clearerr可以清除這兩個標誌。
    從流中讀取數據之後,可以調用ungetc將字符再壓送回流中。
    #include <stdio.h>
    int ungetc(int c,FILE *fp);

    壓送回到流中的字符以後又可從流中讀出,但讀出字符的順序與壓送回的順序相反。
    實際上,每次提供一次只回送一個字符,我們不能期望一次能回送多個字符。
    回送的字符,不一定必須是上一次讀到的字讀。不能回送EOF。但是當已經到達文件尾端時,仍可以回送一個字符。
    下次讀將返回該字符,再讀則返回EOF。之所以能這樣做的原因時,一次成功的ungetc調用會清除該流的文件結束
    標誌。

2、輸出函數:
    #include <stdio.h>
    int putc(int c,FILE *fp);
    int fputc(int c, FILE *fp);
    int putchar(int c);
    與輸出函數一樣,putchar(c)等同於putc(stdin),putc可被實現爲宏,而fputc不能

每次一行I/O:
    #include <stdio.h>
    char *fgets(char *restrict buf,int n,FILE *restrict fp);
    char *gets(char *buf);
    這兩個函數都指定了緩衝區的地址,讀入的行將送入其中。gets從標出輸入讀,而fgets則從指定的流讀。

    對於fgets,必須指定緩衝的長度n。此函數一直讀到下一個換行符爲止,但是不超過n-1個字符,讀入的字符將
    被送入緩衝區。該緩衝區以null字節結尾。如若該行包括最後一個換行符的字符數超過n-1,則fgets只返回一個
    不完整的行,但是,緩衝區總是以null字節結尾。對fgets的下一次調用會繼續讀該行。

    gets是一個不推薦使用的函數。其問題時調用者再使用gets時不能指定緩衝區的長度。這樣就可能趙成緩衝區溢出
    (如若改行長於緩衝區長度),寫道緩衝區之後的存儲空間中,從而產生不可預料的結果。

    gets與fgets的另一個區別是,gets並不將換行符存入緩衝區中。

    fputs和puts函數提供了每次輸出一行的功能:
    #include <stdio.h>
    int fputs(const char *restrict str,FILE *restrict fp);
    int puts(const char *str);
    函數fputs函數將一個null字節終止的字符串寫道指定的流,尾端的終止符null不寫出。
    注意,這並不一定是每次輸出一行,因爲字符串不需要換行符作爲最後一個非null字節。通常,在null字節之前是一個換行符
    但並不要求總是如此。
    puts將一個null字節終止的字符串寫到標準輸出,終止符不寫出。但是,puts隨後又將一個換行符寫到標準輸出。

標準I/O的效率:
    使用getc和putc函數將標準輸入複製到標準輸出。
        #include <stdio.h>
        #include <stdlib.h>

        int main()
        {
            int c;
            while((c=getc(stdin))!=EOF)
            {
                if(putc(c,stdout)==EOF)
                        printf("output error");
            }

            if(ferror(stdin))
                printf("input error");
            exit(0);
        }
    可以使用fgetc和fputc函數改寫該程序,這兩個一定是函數,而不是宏。
    最後編寫一個讀、寫行的版本。
        #include <stdio.h>
        #include <stdlib.h>

        #define MAXLINE 100
        int main()
        {
            char buf[MAXLINE];

            while(fgets(buf,MAXLINE,stdin)!=NULL)
            {
                if(fputs(buf,stdout)==EOF)
                        printf("out error");
            }
            if(ferror(stdin))
                printf("input error");
            exit(0);
        }
    實際上,每次一行的版本會更慢一些。而在本測試中所用的每次一行函數使用memccpy實現的,通常爲了提高效率,
    memccpy用匯編實現而不是c語言實現的。

二進制I/O:
    如果進行二進制I/O操作,那麼我們更願意一次讀或寫一個完整的結構。如果使用getc或putc讀、寫一個結構,那麼
    必須循環通過整個結構,每次循環處理一個字節,一次讀或寫一個字節,這會非常麻煩而且費時。如果使用fgets和
    fputs,那麼因爲fputs再遇到null字節時就停止,而在結構中可能包含null字節,所有不能使用它實現讀結構的
    要求。同樣,如果輸入數據中包含null字節或換行符,則fgets也不能正確工作。因此,提供了下列函數:

    #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 nojb,FILE *restrict fp);
    返回值:讀或者寫的對象數。   

    這些函數由以下兩種常見的用法:
    1、讀或者寫一個二進制數組。例如,爲了將一個浮點數組的第2~5個元素寫至一文件上,編寫如下:
        float data[10];
        if(fwrite(&data[2],sizeof(float),4,fp)!=4)
            printf("fwrite error");

    2、讀或者寫一個結構。例如編寫如下:
        struct {
            short count;
            long total;
            char name[NAMESIZE];
        }item;
        if(fwrite(&item,sizeof(item),1,fp)!=1)
            printf("fwrite error");
    其中size爲結構的長度,nobj爲1(要寫的對象個數)。

    對於讀,如果出錯或到達文件尾端,則此數字可以少於nobj。在這種情況下,應調用ferror或者feof一判斷究竟是哪一種情
    況。
    對於寫,如果返回值少於所要求的nobj,則出錯。

    但是它們只能用於讀在同一系統上已寫的數據。在另外一個系統上,可能不能正常工作,原因是:
    1、在一個結構中,同一成員的偏移量可能隨編譯程序和系統的不同而不同。
    2、用來存儲多字節整數和浮點值的二進制格式在不同的系統結構間也可能不同。


定位流:
    1、ftell和fseek函數。但是它們都假定文件的位置可以存放在一個長整型中。
    2、ftello和fseeko函數。使文件偏移量可以不必一定使用長整型。它們使用off_t數據類型代替了長整型。
    3、fgetpos和fsetpos函數。它們使用一個抽象數據類型fpos_t記錄文件的位置。這種數據類型可以根據需要定義
    一個足夠大的數,用以記錄文件位置。

    需要在非UNIX系統上運行的應用程序應當使用fgetpos和fsetpos。
    #include <stdio.h>
    long ftell(FILE *fp);   成功:返回當前文件位置指示,出錯,返回-1L
    int fseek(FILE *fp,long offset,int whence); 成功:返回0,出錯,返回-1
    void rewind(FILE *fp);
    對於一個二進制文件,其文件位置指示器就是從文件起始位置開始度量,並以字節爲度量單位的。
    ftell用於二進制文件時,其返回值就是這種字節位置。
    爲了用fseek定位一個二進制文件,必須指定一個字節offset,以及解釋這種偏移量的方式。
    whence的值:SEEK_SET表示從文件的起始位置開始,
        SEEK_CUR從當前位置開始,
        SEEK_END從文件尾端開始。


    爲了定位一個文本文件,whence一定要時SEEK_SET,而且offset只能有兩種值:0(後退到文件的起始位置)
    或是對該文件的ftell所返回的值,使用rewind也可將一個流設置到文件的起始位置。

    除了偏移量的類型是off_t而非long外,ftello函數與ftell相同,fseeko函數與fseek相同。

    #include <stdio.h>
    off_t ftello(FILE *fp);成功:返回當前文件位置,出錯,返回(off_t)-1
    int fseeko(FILE *fp,off_t offset,int whence);成功:返回0,出錯,返回-1

    fgetpos和fsetpos函數:
    #include <stdio.h>
    int fgetpos(FILE *restrict fp,fpos_t *restrict pos);
    int fsetpos(FILE *fp,const fpos_t *pos);
    fgetpos將文件位置指示器的當前值存入由pos指向的對象中。在之後使用fsetpos時,可以使用此值將流
    重新定位至該位置。

格式化I/O:
    1、格式化輸出
    格式化輸出是由5個printf函數來處理的:
    #include <stdio.h>
    int printf(const char *restrict format,...);
    int fprintf(FILE *restrict fp,const char *restrict format,...);
    int dprintf(int fd,const char *restrict fromat,...);
    3個函數返回值:成功,返回輸出字符數,錯誤,返回負值
    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字節
    但該字符不包括在返回值中。

    雖然dprintf不處理文件指針,但我們仍然把它包括在處理格式化輸出的函數中。注意,使用dprintf
    不需要使用fdopen將文件描述符轉換爲文件指針(fprintf需要)。

    fldwidth說明最小字段寬度。轉換後參數字符數若小於寬度,則多餘字符位置用空格填充。字段寬度是一個
    非負十進制數,或是一個星號(*).

    precision說明整形轉換後最小輸出數字位數、浮點數轉換後小數點後的最少位數、字符串轉換後最大字節數。
    精度是一個點(.),其後跟隨一個可選的非負十進制數或者一個星號(*).

    寬度和精度字段兩者皆可爲*.此時,一個整形參數指定寬度或精度的值。該整形參數正好位於被轉換的參數之前。
    lenmodifier說明參數長度。其可能的值:

    convtype不是可選的。它控制如何解釋參數。

    printf族的變體類型於上面的5種,但是可變參數表(...)替換成了arg:
    #include <stdarg.h>
    #include <stdio.h>

    int vprintf(const char *restrict format,va_list arg);
    int vfprintf(FILE *restrict fp,const char *restrict format,va_list arg);
    int vdprintf(int fd,const char *restrict format,va_list arg);
    3個函數的返回值:成功,返回輸出字符數,出錯,返回負值
    int vsprintf(char *restrict buf,const char *restrict format,va_list arg);
    int vsnprintf(char *restrict buf ,size_t n,const char *restrict format,va_list arg);
    函數返回值:緩衝區足夠大,返回存入數組的字符數,出錯,返回負值。

格式化輸入:
    #include <stdio.h>
    int scanf(const char *restrict format,...);
    int fscanf(FILE *restrict fp,const char *restrict format,...);
    int sscanf(const char *restrict buf,const char *restrict format,...);
    返回值:賦值的輸入項數;出錯或到達文件尾端,返回EOF

    scanf族的變體類型:
    #include <stdarg.h>
    #include <stdio.h>
    int vscanf(const char *restrict format,va_list arg);
    int vfscanf(FILE *restrict fp,const char *restrict format,va_list arg);
    int vsscanf(const char *restrict buf,const char *restrict format,va_list arg);

實現細節:
    每個標準I/O流都有一個與其相關聯的文件描述符,可以對一個流調用fileno函數以獲得其描述符:
    #include <stdio.h>
    int fileno(FILE *fp);
    返回值:與該流相關聯的文件描述符。

    如果要調用dup或者fcntl函數,則需要此函數。

臨時文件:
    標準I/O庫提供了兩個函數以幫助創建臨時文件:
    #include <stdio.h>
    char *tmpnam(char *ptr);
    FILE *tmpfile(void);

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

    若ptr是NULL,則所產生的路徑名存放在一個靜態區中,指向該靜態區的指針作爲函數值返回。後續調用temnam時,
    會重寫該靜態區(這意味着,如果我們調用此函數多次,而且想保存路徑名,則我們應當保存該路徑名的副本,而不是
    指針的副本)。如若ptr不是NULL,則認爲它應該是指向長度至少是L_tmpnam個字符的數組(常量L_tmpnam定義在文件
    <stdio.h>中)。所產生的路徑名存放在該數組中,ptr也作爲函數值返回。

    tmpfile創建一個臨時的二進制文件(類型wb+),在關閉該文件或者程序結束時自動刪除這個文件。注意UNIX對二進制文件
    不進行特殊區分。
        #include <stdio.h>
        #include <stdlib.h>

        #define MAXLINE 100

        int main()
        {
            char name[L_tmpnam],line[MAXLINE];
            FILE *fp;

            printf("%s\n",tmpnam(NULL));

            tmpnam(name);
            printf("%s\n",name);

            if((fp=tmpfile())==NULL)
                printf("tmpfile error");
            fputs("one line of output\n",fp);
            rewind(fp);
            if(fgets(line,sizeof(line),fp)==NULL)
                printf("fgets error");
            fputs(line,stdout);
            exit(0);
        }
        執行程序:
        ./a.out

    tmpfile函數經常使用的標準UNIX技術時先調用tmpnam產生一個唯一的路徑名,然後,用該路徑名創建一個文件,
    並立即unlink它。對一個文件解除鏈接並不刪除其內容,關閉該文件時才刪除其內容。而關閉文件可以是顯式的,
    也可以在程序終止時自動進行。

    處理林是文件定義了另外兩個函數:mkdtemp和mkstemp:
    #include <stdlib.h>
    char *mkdtemp(char *template);
    返回值:若成功,返回指向目錄名的指針,出錯,返回NULL
    int mkstemp(char *template);
    返回值:若成功,返回文件描述符,出錯,返回-1

    mkdtemp函數創建一個目錄,該目錄由一個唯一的名字;mkstemp函數創建一個文件,該文件有一個唯一的名字。
    名字時通過template字符串進行選擇的。這個字符串時後6爲設置爲XXXXX的路徑名。函數將這些佔位符替換成不
    同的字符來構建一個唯一的路徑名。如果成功的話,這兩個函數將修改template字符串反映臨時文件的名字。

    由mkdtemp函數創建的目錄使用下列訪問權限位集:S_IRUSR | S_IWUSR | S_IXUSR。注意,調用進程的文件
    模式創建屏蔽字可以進一步限制這些權限。如果目錄創建成功,mkdtemp返回新目錄的名字。

    mkstemp函數以唯一的名字創建一個普通文件並且打開該文件,該函數返回的文件描述符以讀寫方式打開。由mkstemp
    創建的文件使用訪問權限位S_IRUSR | S_IWUSR。

    與tempfile不同,mkstemp創建的臨時文件並不會自動刪除。如果希望從文件系統命名空間中刪除該文件,必須自己對
    它解除鏈接。

    使用tmpnam和tempnam至少有一個缺點:在返回唯一的路徑名和用該名字創建文件之間存在一個時間窗口,在這個時間
    窗口中,另一個進程可以用相同的名字創建文件。以此應該使用它們tmpfile和mkstemp函數,因爲它們不存在這個問題。

        #include <stdio.h>
        #include <stdlib.h>
        #include <errno.h>
        #include <sys/stat.h>

        void make_temp(char *template);

        int main()
        {
            char good_template[]="/tmp/dirXXXXXX";
            char *bad_template="/tmp/dirXXXXXX";

            printf("trying to create first temp file...\n");
            make_temp(good_template);
            printf("trying to crreate second temp file ...\n");
            make_temp(bad_template);
            exit(0);
        }

        void make_temp(char *template)
        {
            int fd;
            struct stat sbuf;

            if((fd=mkstemp(template))<0)
                printf("can't create temp file");
            printf("temp name = %s\n",template);

            close(fd);

            if(stat(template,&sbuf)<0){
                if(errno==ENOENT)
                        printf("file doesn't exist\n");
                else
                        printf("stat failed");

            }
            else{
                printf("file exists\n");
                unlink(template);
            }
        }

        執行就會發現:這兩個模板字符串聲明方式的不同帶來了不同的結果。
        對於第一個模板,因爲使用了數組,名字是在棧上分配的。
        而第二個使用的是指針,只有指針自身駐留在棧上。
        編譯器把字符串存放在可執行文件的只讀段,當mkstemp函數試圖修改字符串時,出現了
        段錯誤。

內存流:
    標準I/O庫是數據緩存在內存中,因此每次一字符和每次一行的I/O更有效。
    我們也可以通過調用setbuf或者setvbuf函數讓I/O庫使用我們自己的緩衝區。
    在SUSv4中支持了內存流。這就是標準I/O流,雖然仍使用FILE指針進行訪問,但起始並沒有底層
    文件。所有的I/O都是通過在緩衝區與主存之間來回傳送字節來完成的。我們將看到,即使這些流
    看起來像文件流,我們的某些特徵使其更適合用於字符串操作。

    3個函數可用於內存流的創建,第一個是fmemopen函數:
    #include <stdio.h>
    FILE *fmemopen(void *restrict buf,size_t size,const char *restrict type);
    返回值:成功,返回流指針,出錯,返回NULL

    fmemopen函數允許調用者提供緩衝區用於內存流:buf參數指向緩衝區的開始位置,size參數指向了緩衝區大小
    的字節數。如果buf參數爲空,fmemopen函數分配size字節數的緩衝區。
    在這種情況下,當流關閉時緩衝區會被釋放。
    type參數控制如何使用流:
    type            說明
    r或rb            爲讀而打開
    w或wb            爲寫而打開
    a或ab            追加:爲在第一個null字節處寫而打開
    r+或者r+b或rb+     爲讀和寫而打開
    w+或w+b或wb+      把文件截斷至0長,爲讀和寫而打開
    a+或a+b或ab+      追加:爲在第一個null字節處讀和寫而打開

    注意,這些取值對應於基於文件的標準I/O流的type參數取值,但其中有些微小差別。
    1、無論何時以追加寫方式打開內存流時,當前文件位置設爲緩衝區中的第一個null字節。如果緩衝區中不存在null
    字節,則當前位置就設爲緩衝區結尾的後一個字節。當流並不是以追加寫方式打開時,當前位置設爲緩衝區的開始位置
    因爲追加寫模式通過第一個null字節確定數據的尾端,內存流並不適合存儲二進制數據(二進制數據在數據尾端之前
    就可能包含多個null字節)。
    2、如果buf參數是一個null指針,打開流進行讀或者寫都沒有任何意義。因爲在這種情況下緩衝區是通過fmemopen進行
    分配的,沒有辦法找到緩衝區的地址,只寫方式打開流意味者無法讀取已寫入的數據,同樣,以讀方式打開意味着只能
    讀取那些我們寫入的緩衝區的中的數據
    3、任何時候需要增加流緩衝區中數據量以及調用fclose、fflush、fseek、fseeko以及fsetpos是都會在當前位置寫入
    一個null字節。

    實例:
    有必要看一下對內存流的寫入是如何在我們自己提供的惡緩衝區上進行操作的:
        #include <stdio.h>
        #include <stdlib.h>

        #define BSZ 48

        int main()
        {
            FILE *fp;
            char buf[BSZ];

            memset(buf,'a',BSZ-2);
            buf[BSZ-2]='\0';
            buf[BSZ-1]='X';

            if((fp=fmemopen(buf,BSZ,"w+"))==NULL)
                printf("fmemopen failed");
            printf("inital buffer contents: %s\n",buf);
            fprintf(fp,"hello world");
            printf("before flush: %s\n",buf);
            fflush(fp);
            printf("after flush: %s\n",buf);
            printf("len of string in buf= %ld\n",(long)strlen(buf));

            memset(buf,'b',BSZ-2);
            buf[BSZ-2]='\0';
            buf[BSZ-1]='X';
            fprintf(fp,"hello wordld");
            fseek(fp,0,SEEK_SET);
            printf("after fseek: %s\n",buf);
            printf("len of string in buf= %ld\n",(long)strlen(buf));

            memset(buf,'c',BSZ-2);
            buf[BSZ-2]='\0';
            buf[BSZ-1]='X';
            fprintf(fp,"hello world");
            fclose(fp);
            printf("after fclose: %s\n",buf);
            printf("len of string in buf=%ld\n",(long)strlen(buf));

            return(0);


        }
        執行:
        ./beuffered
    這個例子給出了沖洗內存流和追加寫null字節的策略。寫入內存流以及推進流的內容大小(項對緩衝區大小而言。該
    大小是固定的)這個概念時,null字節會自動追加寫。流內容大小是由寫入多少來確定的。

    用於創建內存流的其他兩個函數分別是open_memstream和open_wmemstream
    #include <stdio.h>
    FILE *open_memstream(char **bufp,size_t *sizep);
    #include <wchar.h>
    FILE *open_wmemstream(wchar_t **bufp,size_t *sizep);
        兩個函數的返回值:成功,返回流指針,出錯,返回NULL

    open_memstream函數創建的流是面向字節的,open_wmemstream函數創建的流是面向寬字節的。與fmemopen函數不同:
    1、創建的流只能寫打開
    2、不能指定自己的緩衝區,但可以分別通過bufp和sizep參數訪問緩衝區地址和大小。
    3、關閉流後需要自行釋放緩衝區
    4、對流添加字節會增加緩衝區大小
    但是在緩衝區地址和大小的使用上必須遵循一些原則。
    1、緩衝區地址和長度只有在調用fclose或fflush後纔有效
    2、這些值只有在下一次流寫入或調用fclose前纔有效。因爲緩衝區可以增長,可能需要重新分配。如果出現這種情況,我們
    會發現緩衝區的內存地址值在下一次調用fclose或fflush是會改變。
發佈了50 篇原創文章 · 獲贊 21 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章