在上一篇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
當 whence
是 SEEK_CUR
或 SEEK_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上找到。
如果覺得本文對你有幫助,請多多點贊支持,謝謝!