在上一篇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
當 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()
將字串(不包括 ` ` )寫入檔案,函式說明如下:
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上找到。
如果覺得本文對你有幫助,請多多點贊支援,謝謝!