Linux編程學習筆記 | Linux IO學習[2] - 標準IO

在上一篇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...\n");
        return -1; 
    } else {
        printf("File create success...\n");
    }
    
    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() 將字符串(不包括 `\0` )寫入文件,函數說明如下:

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

args:
    const char *restrict s: 寫入的字符串
    FILE *restrict stream : 指向寫入文件的文件指針  

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

puts()

puts() 將字符串(不包括 `\0` )寫入 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...\n");
        return -1; 
    } else {
        printf("File create success...\n");
    }
    
    /* 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\n", wr_size); 

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

    /* fputc function */
    char buffer_4[] = "This is fputc DEMO...\n";
    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...\n";
    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 文件中會輸出以下內容,其中 @^ 就是 `\0`

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

注意 fputs 函數并沒有輸出 `\0` 。在終端會輸出:

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...\n");
        return -1; 
    } else {
        printf("File open success...\n");
    }
    
    /* fread() function */ 
    char buffer_1[50]; 
    size_t rd_size = 0; 
    rd_size = fread(buffer_1, 1, 24, fp); 
    printf("rd_size = %d\n", rd_size); 
    printf("fread get: %s\n", 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上找到。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數據革命閱讀 12,203評論 2 33
  • tags:io categories:總結 date: 2017-03-28 22:49:50 不僅僅在JAVA領...
    行徑行閱讀 2,199評論 0 3
  • 賴玉超看鐵打釘~ 2016年2月11日普寧 我媳婦組織同學會去了,是小學同學聚會。 她問我去不? 我也想去,一來我...
    laiyuchao閱讀 257評論 0 0
  • 01 聽說每一個人都會經歷一段受傷的感情才會真正的長大。姐姐在結婚之前遇到了傳說中的渣男友,他們是同事,在一起的時...
    宴宴閱讀 406評論 0 4
  • 傍晚大雨欲勢來,腳踏雨中疾步走。 待到寢室衣裳濕,抬頭眺望遠處景。 空中驚現兩道虹 ,時隔多年從未見。 出門與眾道...
    逐夢人生閱讀 317評論 3 7