在上一篇Linux編程學(xué)習(xí)筆記 | Linux IO學(xué)習(xí)[1] - 文件IO中,我總結(jié)了Linux下的文件IO。文件IO是偏底層的IO操作,在平時(shí)的日常工作中,使用文件IO的頻率還是比較低的。我們每天使用的 printf() 就不是文件IO,而是另一類IO - 標(biāo)準(zhǔn)IO。在這篇文章中,我將介紹Linux下的標(biāo)準(zhǔn)IO并通過實(shí)例來說明如何使用它們。
標(biāo)準(zhǔn)IO庫
要使用標(biāo)準(zhǔn)IO庫,需要包含頭文件 <stdio.h> 。該庫為用戶創(chuàng)建了一個(gè)連接底層系統(tǒng)調(diào)用的通用接口,它是ANSI C標(biāo)準(zhǔn)制定的庫,因此具有可移植性(文件IO是基于Unix的POSIX標(biāo)準(zhǔn),不可移植到Windows)。同文件IO類似,它需要先打開一個(gè)文件以建立一個(gè)訪問途徑,文件IO的訪問途徑是通過文件描述符,標(biāo)準(zhǔn)IO的訪問途徑是流(stream),它被實(shí)現(xiàn)為指向結(jié)構(gòu)FILE的指針。
同文件IO類似,在程序啟動(dòng)時(shí)也有3個(gè)默認(rèn)的文件流:
| 標(biāo)準(zhǔn)流 | 變量或宏 | 說明 |
|---|---|---|
| 0 | stdin | 標(biāo)準(zhǔn)輸入 |
| 1 | stdout | 標(biāo)準(zhǔn)輸出 |
| 2 | stderr | 標(biāo)準(zhǔn)錯(cuò)誤輸出 |
標(biāo)準(zhǔn)IO基本操作
標(biāo)準(zhǔn)IO的函數(shù)相對文件IO來說要多很多,我這里主要介紹13個(gè)標(biāo)準(zhǔn)IO函數(shù)。
打開/創(chuàng)建文件流
fopen() 和文件IO中的 open() 類似,用于打開文件流,函數(shù)說明如下:
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或創(chuàng)建一個(gè)供寫的文件
3. "a" or "ab" : 以寫方式打開文件并將內(nèi)容寫到文件末或創(chuàng)建一個(gè)文件
4. "r+" or "rb+" or "r+b" : 以更新的方式(讀/寫)打開文件(文件必須存在)
5. "w+" or "wb+" or "w+b" : 以更新的方式(讀/寫)打開文件并將文件長度截為0或創(chuàng)建一個(gè)文件
6. "a+" or "ab+" or "a+b" : 以更新的方式(讀/寫)打開文件并將更新內(nèi)容寫到文件末或創(chuàng)建一個(gè)文件
fopen() 和 open() 不同, fopen() 并不能在創(chuàng)建文件時(shí)改變其訪問權(quán)限。
關(guān)閉文件流
fclose() 和文件IO中的 close() 類似,用于關(guān)閉文件流,函數(shù)說明如下:
int fclose(FILE *stream);
args:
FILE *stream: 指向被關(guān)閉文件的指針
return:
關(guān)閉文件成功返回0,關(guān)閉文件失敗返回而EOF
我們來看第一個(gè)例子,文件的打開和關(guān)閉:
#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;
}
運(yùn)行結(jié)果:
新建一個(gè)叫 stdio.log 的文件,并輸出
File create success...
如果我們注釋掉第8行,去掉第7行的注釋,那么將輸出
File create fail... 。
修改文件流讀寫偏移量
fseek() 和文件IO中的 lseek() 類似,用于修改文件流讀寫的偏移量,函數(shù)說明如下:
int fseek(FILE *stream, long offset, int whence);
args:
FILE *stream: 指向文件的文件指針
long offset : 偏移量移動(dòng)的距離
int whence : 偏移量的基址
- SEEK_SET 文件開始處
- SEEK_CUR 文件當(dāng)前位置
- SEEK_END 文件結(jié)束處
return:
修改偏移量成功返回0, 修改偏移量失敗返回-1
當(dāng) whence 是 SEEK_CUR 或 SEEK_END 時(shí), offset 可正負(fù)。
寫文件流
fwrite()
fwrite() 和文件IO中的 write() 類似,函數(shù)說明如下:
size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
args:
const void *restrict ptr: 寫入數(shù)據(jù)在內(nèi)存空間存儲(chǔ)的地址
size_t size : 單個(gè)元素的大小
size_t nitems : 寫入數(shù)據(jù)元素的個(gè)數(shù)
FILE *restrict stream : 指向?qū)懭胛募奈募羔?
return:
實(shí)際寫入的元素個(gè)數(shù),非負(fù)整數(shù)是成功,-1是失敗
fputs()
fputs() 將字符串(不包括 `\0` )寫入文件,函數(shù)說明如下:
int fputs(const char *restrict s, FILE *restrict stream);
args:
const char *restrict s: 寫入的字符串
FILE *restrict stream : 指向?qū)懭胛募奈募羔?
return:
寫入文件的狀態(tài),非負(fù)整數(shù)是成功,EOF是失敗
puts()
puts() 將字符串(不包括 `\0` )寫入 stdout ,并在行末添加一個(gè)換行符,函數(shù)說明如下:
int puts(const char *s);
args:
const char *s: 寫入的字符串
return:
寫出到stdio的狀態(tài),非負(fù)整數(shù)是成功,EOF是失敗
fputc()
fputc() 將一個(gè)字符寫入文件,函數(shù)說明如下:
int fputc(int c, FILE *stream);
args:
int char : 要寫入的字符
FILE *stream: 指向?qū)懭胛募奈募羔?
return:
如果沒有錯(cuò)誤,返回寫入的字符,否則返回EOF
putc()
putc() 和 fputc() 基本一樣,只不過 putc() 是用宏實(shí)現(xiàn)而 fputc 是用函數(shù)實(shí)現(xiàn)。
int putc(int c, FILE *stream);
args:
int c : 要寫入的字符
FILE *stream: 指向?qū)懭胛募奈募羔?
return:
如果沒有錯(cuò)誤,返回寫入的字符,否則返回EOF
我們通過例子來看看上面這幾個(gè)函數(shù)的使用方法:
#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;
}
運(yùn)行結(jié)果:
在生成的 std_io.log 文件中會(huì)輸出以下內(nèi)容,其中 @^ 就是 `\0`
This is fwrite DEMO...^@
This is fputs DEMO...
This is fputc DEMO...
^@This is putc DEMO...
^@
注意 fputs 函數(shù)并沒有輸出 `\0` 。在終端會(huì)輸出:
File create success...
wr_size = 23
fputs_status = 23
This is puts DEMO...
This is fputc DEMO...
This is putc DEMO...
puts 函數(shù)直接將字符串輸出到 stdio 。
讀文件流
fread()
fread() 和文件IO中的 read() 類似,函數(shù)說明如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
args:
void *ptr : 讀取數(shù)據(jù)存儲(chǔ)的內(nèi)存空間的地址
size_t size : 單個(gè)元素的大小
size_t nmemb: 讀取數(shù)據(jù)元素的個(gè)數(shù)
FILE *stream: 指向讀取文件的文件指針
return:
實(shí)際讀取的元素個(gè)數(shù),非負(fù)整數(shù)是成功,-1是失敗
fgets()
fgets() 用于讀取文件中的字符串,然后將其存儲(chǔ)到內(nèi)存空間,函數(shù)說明如下:
char *fgets(char *restrict s, int n, FILE *restrict stream);
args:
char *restrict s : 讀取后字符串存儲(chǔ)的內(nèi)存空間地址
int n : 最大讀取字符數(shù)
FILE *restrict stream: 指向讀取文件的文件指針
return:
如果讀取沒有錯(cuò)誤且沒有讀入EOF,返回寫入的字符串
如果讀取沒有錯(cuò)誤但讀入EOF,返回NULL指針
如果讀取出現(xiàn)錯(cuò)誤,返回NULL指針
這里需要注意下該函數(shù)將在何時(shí)停止讀取:
如果讀取的字符數(shù)量達(dá)到 n - 1 ,或讀取了換行符,或讀取了字符串結(jié)束符,只要有一個(gè)滿足則該函數(shù)會(huì)停止繼續(xù)讀取。
gets()
gets() 從 stdin 中讀取字符串并存放在內(nèi)存中,函數(shù)說明如下:
char *gets(char *s);
args:
char *s: 讀取后字符串存儲(chǔ)的內(nèi)存空間地址
return:
如果讀取沒有錯(cuò)誤且沒有讀入EOF,返回讀取的字符串
如果讀取沒有錯(cuò)誤但讀入EOF,返回NULL指針
如果讀取出現(xiàn)錯(cuò)誤,返回NULL指針
讀取操作將在讀入換行符或EOF后結(jié)束。
fgetc()
fgetc() 從一個(gè)文件讀取一個(gè)字符,函數(shù)說明如下:
int fgetc(FILE *stream);
args:
FILE *stream: 指向讀取文件的文件指針
return:
如果讀取沒有錯(cuò)誤且沒有讀入EOF,返回讀取的字符
如果讀取沒有錯(cuò)誤但讀入EOF,返回EOF
如果讀取出現(xiàn)錯(cuò)誤,返回EOF
getc()
getc() 和 fgetc() 基本一樣,只不過 getc() 是用宏實(shí)現(xiàn)而 fgetc() 是用函數(shù)實(shí)現(xiàn)。
int getc(FILE *stream);
args:
FILE *stream: 指向讀取文件的文件指針
return:
如果讀取沒有錯(cuò)誤且沒有讀入EOF,返回讀取的字符
如果讀取沒有錯(cuò)誤但讀入EOF,返回EOF
如果讀取出現(xiàn)錯(cuò)誤,返回EOF
說完了讀操作的函數(shù),我們也通過一個(gè)例子來看看如何使用這些函數(shù):
#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;
}
在編譯過程中,編譯器會(huì)警告:
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.
因?yàn)?gets() 函數(shù)過于危險(xiǎn),在C11中的 <stdio.h> 已經(jīng)不再包含 gets() 函數(shù),因此會(huì)出現(xiàn)這個(gè)警告。
運(yùn)行結(jié)果:
要讀取的文件是之前寫函數(shù)生成的 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...
總結(jié)
這篇文章主要介紹了如何使用幾個(gè)基本的標(biāo)準(zhǔn)IO函數(shù)對文件進(jìn)行操作,相對于文件IO而言,標(biāo)準(zhǔn)IO使用更方便,并且支持跨平臺使用。同時(shí)在傳輸大文件時(shí),標(biāo)準(zhǔn)IO也不比文件IO慢。文中出現(xiàn)的代碼都可在我的github上找到。
如果覺得本文對你有幫助,請多多點(diǎn)贊支持,謝謝!