Linux-C語言標准輸入輸出


  標准 I/O 庫(stdio)及其頭文件 stdio.h 為底層 I/O 系統調用提供了一個通用的接口。這個庫現在已經成為 ANSI 標准 C 的一部分。標准 I/O 庫提供了許多復雜的函數用於格式化輸出和掃描輸入。在很多方面,你使用的標准 I/O 庫的方式和使用底層文件描述符一樣,需要先打開一個文件以建立一個訪問路徑,這個操作的返回值將作為其他 I/O 庫函數的參數。在標准 I/O 庫中,與底層文件描述符對應的是(stream,需要注意的是這個流與 C++ 中的輸入輸出流不一樣),它被實現為指向結構 FILE 的指針文件指針)。

  在啟動程序時,有 3 個文件流是自動打開的,它們是 stdin、stdout 和 stderr,在 stdio.h 中定義,分別代表着標准輸入、標准輸出和標准錯誤輸出,與底層文件描述符 0、1、2 相對應。可用的文件流數量與文件描述符一樣,都是有限制的,實際的限制由頭文件 stdio.h 中定義的 FOPEN_MAX 來定義,它的值至少為 8,在 Linux 系統中,通常是 16。

一、fopen 函數

  用於文件和終端的輸入輸出,,類似於系統調用 open。調用 fopen 函數成功時,返回一個非空的 FILE * 指針,調用失敗時,返回 NULL。函數原型如下:

#include <stdio.h>

FILE *fopen(const char *filename,const char *mode);

  關於 FILE 結構,有:

typedef struct _iobuf{
    int   cnt;      // 剩余字符數
    char  *ptr;     // 下一個字符的位置
    char  *base;    // 緩沖區的位置
    int   flag;     // 文件訪問模式
    int   fd;       // 文件描述符
}FILE;

  fopen 打開由 filename 參數指定的文件,並把它與一個文件流(即 fopen 函數的返回值,一個指向 FILE 的指針)關聯起來。mode 參數指定文件打開的方式,可以取以下的一些值:

字符串 說明
"r" 、"rb" 以只讀方式打開文件
"w" 、"wb" 以寫方式打開文件,並把文件長度截短為零
"a" 、"ab" 以寫方式打開文件,新內容追加在文件尾
"r+" 、"rb+" 、"r+b" 以更新方式打開文件(讀和寫)
"w+" 、"wb+" 、"w+b" 以更新方式打開文件,並把文件長度截短為零
"a+" 、"ab+" 、"a+b" 以更新方式打開文件,新內容追加在文件尾

  其中,字母 b 表示文件是一個二進制文件而不是文本文件。另外,需要注意的是,mode 參數是一個字符串而不是一個字符,因此總是需要使用雙引號 " " 括起來。

 

二、fread 函數

  fread 函數用於從一個文件流里讀取數據。調用 fread 成功時,返回成功讀到緩沖區里的記錄個數(不是字節數)。函數原型如下:

#include <stdio.h>

size_t fread(void *ptr,size_t size,size_t nitems,FILE *stream);

   數據從文件流 stream 讀到由 ptr 指向的數據緩沖區里,size 參數指定每個數據記錄的長度,計數器 nitems 給出要傳輸的記錄個數。舉個例子:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        int buffer[10];
        int i;
        int counter;
        counter = fread(buffer,1,10,stdin);
        printf("讀取到緩沖區的記錄個數為:%d\n",counter);
        fwrite(buffer,sizeof(int),1,stdout);

        exit(0);
}

  這段代碼從標准輸入讀取 10 個 字符到緩沖區中,然后調用 fwrite 函數將緩沖區中的前 4 個字符寫到標准輸出。

 

三、fwrite 函數

  fwrite 函數從指定的數據緩沖區里取出數據記錄,並把它們寫到輸出流中,返回成功寫入的記錄個數,其參數與 fread 函數的參數類似。

#include <stdio.h>

siize_t fwrite(const void *ptr,size_t size,size_t nitems,FILE *stream);

  需要注意的是,用 fwrite 寫的文件在不同的計算機體系結構之間可能不具備可移植性,因此,不推薦把 fread 和 fwrite 用於結構化數據。演示代碼如下:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        char buffer[] = "hello,my name is tongye!";
        fwrite(buffer,1,sizeof(buffer),stdout);
        exit(0);
}

/* 輸出結果: 
hello,my name is tongye!
*/

  這段代碼將 buffer 中的內容寫入標准輸出。

 

四、fclose 函數

  fclose 函數關閉指定的文件流 stream,使所有尚未寫出的數據都寫出。因為 stdio 庫會對數據進行緩沖,所以使用 fclose 是很重要的。如果程序需要確保數據已全部寫出,則應該調用 fclose 函數。

#include <stdio.h>

int fclose(FILE *stream);

 

五、fflush 函數

  fflush 函數的作用是把文件流里所有未寫出的數據立刻寫出。

#include <stdio.h>

int fflush(FILE *stream);

  在 調用 fclose 函數時,隱含執行了一次 flush 操作,因此不需要再調用 fclose 之前調用 fflush。

 

六、fseek 函數

  fseek 函數是與 lseek 系統調用對應的文件流函數。它在文件流里為下一次讀寫操作指定位置。當調用 fseek 成功時,返回 0,調用失敗時 返回 -1 並設置 errno 指出錯誤。

#include <stdio.h>

int fseek(FILE *stream,long int offset,int whence);

 

七、fgetc、getc 和 getchar 函數

  fgetc 函數從文件流里取出下一個字節,並把它作為一個字符返回(返回的是該字符的 ASCII 碼)。當它到達文件尾或出現錯誤時,返回 EOF(在 stdio.h 中有定義: #define EOF -1)。必須通過 ferror 或 feof 來區分這兩種情況。函數原型如下:

#include <stdio.h>

int fgetc(FILE *stream);    // 注意,返回值是一個字符,但是這里用 int 類型來存放字符而不是用 char 類型
int getc(FILE *stream);
int getchar();

  getc 函數的作用和 fgetc 函數一樣,但是,getc 函數可以被實現為宏,因此:

1)getc 函數的參數不應該是具有副作用(如影響變量)的表達式,因為它可能會被計算多次;

2)fgetc 一定是一個函數,因此它的地址可以作為一個參數傳遞給另一個函數;而 gets 函數則不能保證其地址一定能作為一個函數指針傳遞給另一個函數;

3)由於 getc 函數可以被實現為宏,因此調用 getc 函數所用的時間可能會比 fgetc 要短。

  getchar 函數的作用就相當於 getc(stdin),它直接從標准輸入里讀取下一個字符。

注意:為什么返回值是四個字節的 int 類型?

  這是為了考慮文件結束標志 EOF 的取值。EOF 取值是 -1,如果用 unsigned 類型的話,顯然取不到 -1;而如果用 char 類型的話,則 -1 對應的值為 0xff(C 語言中,數值以補碼形式存儲),但是 0xff 又可以是一個字節的 ASCII 碼值(一些擴展字符的 ASCII > 127溢出時,可能會產生值為 0xff 的ASCII 碼),這樣用 EOF 顯然就不能判斷文件是否結束了,因為會把 ASCII 碼值為 0xff 的字節誤判為文件結束符。如果將返回值用 int 類型來存放的話,那么 EOF(也就是 -1)將會被保存為 0xffffffff,這時在讀到 0xff,用 int 類型進行存儲的話,就是 0x000000ff,就不會和 EOF 相沖突了。

 

八、fputc、putc 和 putchar 函數

  fputc 函數把一個字符寫到一個輸出文件流中。它返回寫入的值,如果失敗,則返回 EOF。

#include <stdio.h>

int fputc(int c,FILE *stream);    // 需要注意的是,這里的 c 其實是字符,把字符當做 int 類型而不是 char 類型
int putc(int c,FILE *stream);
int putchar(int c);

  putc 函數的作用與 fputc 函數一樣,但是它能被實現為一個宏。putchar 函數相當於 putc(c,stdout),它把單個字符寫到標准輸出。 

  注意:putchar 和 getchar 都是把字符當做 int 類型而不是 char 類型來使用的,理由上面有講。用一個例子演示一下 fgetc 和 fputc 函數的用法:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        int buf;
        FILE *file = fopen("hello.txt","r");
        if(file == NULL){
                printf("fail to open the file!\n");
                exit(1);
        }

        while((buf = fgetc(file)) != EOF){
                fputc(buf,stdout);
        }
fclose(file); exit(
0); } /* 輸出結果: hello,my name is tongye! 2018.10.26 */

  這段代碼打開文件 hello.txt,並使用 fgetc 函數將文件中的內容一個字節一個字節地取出,同時使用 fputc 函數將該字節寫到標准輸出流中,直到文件結束。

 

九、fgets 和 gets 函數

  fgets 函數從輸入文件流 stream 里讀取一個字符串,並存放到緩沖區中,一次讀取的字符個數有限制。

#include <stdio.h>

char *fgets(char *s,int n,FILE *stream);
char *gets(char *s);

  fgets 函數把讀到的字符寫到 s 指向的字符串里,直到:

1)遇到換行符,則停止讀入字符,並將遇到的換行符一起傳遞給接收字符串,再加上一個表示結尾的空字節 \0;

2)已經傳輸了 n-1 個字符,則加上一個空字節 \0 結尾后,停止讀入字符;

3)到達文件尾(EOF)。

  當成功調用函數時,fgets 返回一個指向字符串 s 的指針。如果文件流已經到達文件尾,fgets 會設置這個文件流的 EOF 標識並返回一個空指針;如果調用函數出錯,則 fgets 返回一個空指針並設置 errno 以指出錯誤的類型。

  gets 函數類似於 fgets,只不過它直接從標准輸入(stdin)讀取數據並丟棄了遇到的換行符,它在接收字符串的尾部加上一個 null 字節、另外,需要注意的是,gets 函數並沒有對傳輸字符的個數做限制,所以它可能會溢出自己的傳輸緩沖區。因此,一般來說,推薦使用 fgets 函數來替代 gets 函數。

 

 

參考資料:

《Linux程序設計 第四版》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM