Linux程式設計學習筆記 | Linux IO學習[2] – 標準IO

RdouTyping發表於2019-05-12

在上一篇Linux程式設計學習筆記 | Linux IO學習[1] – 檔案IO中,我總結了Linux下的檔案IO。檔案IO是偏底層的IO操作,在平時的日常工作中,使用檔案IO的頻率還是比較低的。我們每天使用的 printf() 就不是檔案IO,而是另一類IO – 標準IO。在這篇文章中,我將介紹Linux下的標準IO並通過例項來說明如何使用它們。

標準IO庫

要使用標準IO庫,需要包含標頭檔案 <stdio.h> 。該庫為使用者建立了一個連線底層系統呼叫的通用介面,它是ANSI C標準制定的庫,因此具有可移植性(檔案IO是基於Unix的POSIX標準,不可移植到Windows)。同檔案IO類似,它需要先開啟一個檔案以建立一個訪問途徑,檔案IO的訪問途徑是通過檔案描述符,標準IO的訪問途徑是流(stream),它被實現為指向結構FILE的指標。

同檔案IO類似,在程式啟動時也有3個預設的檔案流:

標準流 變數或巨集 說明
0 stdin 標準輸入
1 stdout 標準輸出
2 stderr 標準錯誤輸出

標準IO基本操作

標準IO的函式相對檔案IO來說要多很多,我這裡主要介紹13個標準IO函式。

開啟/建立檔案流

fopen() 和檔案IO中的 open() 類似,用於開啟檔案流,函式說明如下:

FILE *fopen(const char *restrict pathname, const char *restrict mode);

args:
    const char *restrict pathname: 檔案的路徑
    const char *restrict mode    : 檔案開啟的模式
                                    
return:
    返回指向檔案的指標,指標不為NULL是成功,指標為NULL是失敗

檔案開啟的模式有以下6種:

1. "r" or "rb"            : 以只讀形式開啟文(檔案必須存在)
2. "w" or "wb"            : 以寫方式開啟檔案並將檔案長度截為0或建立一個供寫的檔案 
3. "a" or "ab"            : 以寫方式開啟檔案並將內容寫到檔案末或建立一個檔案
4. "r+" or "rb+" or "r+b" : 以更新的方式(讀/寫)開啟檔案(檔案必須存在)
5. "w+" or "wb+" or "w+b" : 以更新的方式(讀/寫)開啟檔案並將檔案長度截為0或建立一個檔案
6. "a+" or "ab+" or "a+b" : 以更新的方式(讀/寫)開啟檔案並將更新內容寫到檔案末或建立一個檔案

fopen()open() 不同, fopen() 並不能在建立檔案時改變其訪問許可權。

關閉檔案流

fclose() 和檔案IO中的 close() 類似,用於關閉檔案流,函式說明如下:

int fclose(FILE *stream);

args:
    FILE *stream: 指向被關閉檔案的指標 

return:
    關閉檔案成功返回0,關閉檔案失敗返回而EOF

我們來看第一個例子,檔案的開啟和關閉:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;

    //fp = fopen("stdio.log", "r+");
    fp = fopen("stdio.log", "w+");
    if (fp == NULL) {
        printf("File create fail...
");
        return -1; 
    } else {
        printf("File create success...
");
    }
    
    fclose(fp);   

    return 0; 
}

執行結果:
新建一個叫 stdio.log 的檔案,並輸出
File create success...
如果我們註釋掉第8行,去掉第7行的註釋,那麼將輸出
File create fail...

修改檔案流讀寫偏移量

fseek() 和檔案IO中的 lseek() 類似,用於修改檔案流讀寫的偏移量,函式說明如下:

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

args:
    FILE *stream: 指向檔案的檔案指標
    long offset : 偏移量移動的距離
    int whence  : 偏移量的基址
                    - SEEK_SET 檔案開始處
                    - SEEK_CUR 檔案當前位置
                    - SEEK_END 檔案結束處

return:
    修改偏移量成功返回0, 修改偏移量失敗返回-1

whenceSEEK_CURSEEK_END 時, offset 可正負。

寫檔案流

fwrite()

fwrite() 和檔案IO中的 write() 類似,函式說明如下:

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

args:
    const void *restrict ptr: 寫入資料在記憶體空間儲存的地址
    size_t size             : 單個元素的大小
    size_t nitems           : 寫入資料元素的個數
    FILE *restrict stream   : 指向寫入檔案的檔案指標 

return:
    實際寫入的元素個數,非負整數是成功,-1是失敗

fputs()

fputs() 將字串(不包括 ` ` )寫入檔案,函式說明如下:

int fputs(const char *restrict s, FILE *restrict stream);

args:
    const char *restrict s: 寫入的字串
    FILE *restrict stream : 指向寫入檔案的檔案指標  

return:
    寫入檔案的狀態,非負整數是成功,EOF是失敗

puts()

puts() 將字串(不包括 ` ` )寫入 stdout ,並在行末新增一個換行符,函式說明如下:

int puts(const char *s);

args:
    const char *s: 寫入的字串

return:
    寫出到stdio的狀態,非負整數是成功,EOF是失敗

fputc()

fputc() 將一個字元寫入檔案,函式說明如下:

int fputc(int c, FILE *stream);

args:
    int char    : 要寫入的字元
    FILE *stream: 指向寫入檔案的檔案指標 

return:
    如果沒有錯誤,返回寫入的字元,否則返回EOF

putc()

putc()fputc() 基本一樣,只不過 putc() 是用巨集實現而 fputc 是用函式實現。

int putc(int c, FILE *stream);

args:
    int c       : 要寫入的字元
    FILE *stream: 指向寫入檔案的檔案指標 

return:
    如果沒有錯誤,返回寫入的字元,否則返回EOF

我們通過例子來看看上面這幾個函式的使用方法:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;

    fp = fopen("stdio.log", "w+");
    if (fp == NULL) {
        printf("File create fail...
");
        return -1; 
    } else {
        printf("File create success...
");
    }
    
    /* fwrite() function */ 
    char buffer_1[] = "This is fwrite DEMO..."; 
    size_t wr_size = 0; 
    wr_size = fwrite(buffer_1, 1, sizeof(buffer_1), fp); 
    printf("wr_size = %d
", wr_size); 

    /* fputs() function */ 
    char buffer_2[] = "
This is fputs DEMO...
"; 
    int fputs_status = 0; 
    fputs_status = fputs(buffer_2, fp); 
    printf("fputs_status = %d
", wr_size); 
    
    /* puts function */
    char buffer_3[] = "This is puts DEMO..."; 
    puts(buffer_3);

    /* fputc function */
    char buffer_4[] = "This is fputc DEMO...
";
    int ret;
    for (int i = 0; i < sizeof(buffer_4); i++) {
        ret = fputc(buffer_4[i], fp);
        printf("%c", ret);
    }

    /* putc function */
    char buffer_5[] = "This is putc DEMO...
";
    for (int i = 0; i < sizeof(buffer_5); i++) {
        ret = fputc(buffer_5[i], fp);
        printf("%c", ret);
    }
    
    fclose(fp);   

    return 0; 
}

執行結果:
在生成的 std_io.log 檔案中會輸出以下內容,其中 @^ 就是 ` `

This is fwrite DEMO...^@
This is fputs DEMO...
This is fputc DEMO...
^@This is putc DEMO...
^@

注意 fputs 函式並沒有輸出 ` ` 。在終端會輸出:

File create success...
wr_size = 23
fputs_status = 23
This is puts DEMO...
This is fputc DEMO...
This is putc DEMO...

puts 函式直接將字串輸出到 stdio

讀檔案流

fread()

fread() 和檔案IO中的 read() 類似,函式說明如下:

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

args:
    void *ptr   : 讀取資料儲存的記憶體空間的地址  
    size_t size : 單個元素的大小
    size_t nmemb: 讀取資料元素的個數
    FILE *stream: 指向讀取檔案的檔案指標

return:
    實際讀取的元素個數,非負整數是成功,-1是失敗

fgets()

fgets() 用於讀取檔案中的字串,然後將其儲存到記憶體空間,函式說明如下:

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

args:
    char *restrict s     : 讀取後字串儲存的記憶體空間地址 
    int n                : 最大讀取字元數
    FILE *restrict stream: 指向讀取檔案的檔案指標

return:
    如果讀取沒有錯誤且沒有讀入EOF,返回寫入的字串
    如果讀取沒有錯誤但讀入EOF,返回NULL指標
    如果讀取出現錯誤,返回NULL指標

這裡需要注意下該函式將在何時停止讀取:
如果讀取的字元數量達到 n - 1 ,或讀取了換行符,或讀取了字串結束符,只要有一個滿足則該函式會停止繼續讀取。

gets()

gets()stdin 中讀取字串並存放在記憶體中,函式說明如下:

char *gets(char *s);

args:
    char *s: 讀取後字串儲存的記憶體空間地址  

return:
    如果讀取沒有錯誤且沒有讀入EOF,返回讀取的字串
    如果讀取沒有錯誤但讀入EOF,返回NULL指標
    如果讀取出現錯誤,返回NULL指標

讀取操作將在讀入換行符或EOF後結束。

fgetc()

fgetc() 從一個檔案讀取一個字元,函式說明如下:

int fgetc(FILE *stream);

args:
    FILE *stream: 指向讀取檔案的檔案指標 

return:
    如果讀取沒有錯誤且沒有讀入EOF,返回讀取的字元
    如果讀取沒有錯誤但讀入EOF,返回EOF
    如果讀取出現錯誤,返回EOF

getc()

getc()fgetc() 基本一樣,只不過 getc() 是用巨集實現而 fgetc() 是用函式實現。

int getc(FILE *stream);

args:
    FILE *stream: 指向讀取檔案的檔案指標 

return:
    如果讀取沒有錯誤且沒有讀入EOF,返回讀取的字元
    如果讀取沒有錯誤但讀入EOF,返回EOF
    如果讀取出現錯誤,返回EOF

說完了讀操作的函式,我們也通過一個例子來看看如何使用這些函式:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;
    
    fp = fopen("stdio.log", "r+");
    if (fp == NULL) {
        printf("File open fail...
");
        return -1; 
    } else {
        printf("File open success...
");
    }
    
    /* fread() function */ 
    char buffer_1[50]; 
    size_t rd_size = 0; 
    rd_size = fread(buffer_1, 1, 24, fp); 
    printf("rd_size = %d
", rd_size); 
    printf("fread get: %s
", buffer_1);

    /* fgets() function */ 
    char buffer_2[50];
    char *fgets_status; 
    fgets_status = fgets(buffer_2, 23, fp); 
    printf("fgets_status = %s", fgets_status); 
    printf("fgets get: %s", buffer_2);
    
    /* gets function */
    char buffer_3[50];
    gets(buffer_3);
    printf("gets get: %s", buffer_3);

    /* fgetc function */
    int ret;
    while ((ret = fgetc(fp)) != EOF)
       printf("%c", ret);

    fclose(fp);   

    return 0; 
}

在編譯過程中,編譯器會警告:

warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
     gets(buffer_3);
     ^~~~
/tmp/cc3YWk3i.o: In function `main`:
fread_get.c:(.text+0x11d): warning: the `gets` function is dangerous and should not be used.

因為 gets() 函式過於危險,在C11中的 <stdio.h> 已經不再包含 gets() 函式,因此會出現這個警告。
執行結果:
要讀取的檔案是之前寫函式生成的 stdio.log 檔案,終端輸出如下。

File open success...
rd_size = 24
fread get: This is fwrite DEMO...
fgets_status = This is fputs DEMO...
fgets get: This is fputs DEMO...
test
gets get: testThis is fputc DEMO...
This is putc DEMO...

總結

這篇文章主要介紹瞭如何使用幾個基本的標準IO函式對檔案進行操作,相對於檔案IO而言,標準IO使用更方便,並且支援跨平臺使用。同時在傳輸大檔案時,標準IO也不比檔案IO慢。文中出現的程式碼都可在我的github上找到。

如果覺得本文對你有幫助,請多多點贊支援,謝謝!

相關文章