Linux系統(tǒng)下C語言中的標(biāo)準(zhǔn)IO總結(jié)
本文對(duì) Linux 下C語言的標(biāo)準(zhǔn)IO進(jìn)行總結(jié),所有代碼示例均在 Ubuntu-20.04、GCC 11.3.0 環(huán)境下運(yùn)行通過。
標(biāo)準(zhǔn)IO中的一些概念
流和FILE對(duì)象
在 Linux 操作系統(tǒng)中,提供給用戶操作文件的接口是“文件描述符”以及對(duì)應(yīng)的函數(shù),例如 read,write等。而在C語言中,提供給用戶的文件操作的接口是“流(stream)”,當(dāng)使用C語言中的標(biāo)準(zhǔn)I/O庫打開或創(chuàng)建一個(gè)文件時(shí),就使得一個(gè)流與一個(gè)文件關(guān)聯(lián)起來。而這個(gè) “流” 的概念,在程序上,使用 FILE 對(duì)象來表示,例如:
#include <stdio.h> int main() { // 打開或者創(chuàng)建一個(gè)文件,使用 FILE 對(duì)象與該文件進(jìn)行綁定。 // 也常把 fp 叫為 文件流 FILE* fp = fopen("example.txt", "a"); // ... }
注意:一個(gè)進(jìn)程預(yù)定了三個(gè)流,標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤。而本文的重點(diǎn)討論對(duì)象是 文件流,也即 FILE 對(duì)象以及標(biāo)準(zhǔn)I/O中提供的操作 FILE 對(duì)象的一系列函數(shù)。
流的定向(stream’s orientation)
對(duì)于ASCII字符集,一個(gè)字符用一個(gè)字節(jié)表示。對(duì)于國際字符集,一個(gè)字符可用多個(gè)字節(jié)表示。標(biāo)準(zhǔn)I/O文件流可用于單字節(jié)或多字節(jié)(也稱“寬”字符)字符集。流的定向(stream’s orientation)決定所讀、寫的字符是單字節(jié)還是多字節(jié)的。
下面給出一個(gè)寬字符集輸出到標(biāo)準(zhǔn)輸出的示例:
#include <iostream> #include <cstring> int main() { const char* charString = "你好,世界!"; std::cout << "Length of charString: " << std::strlen(charString) << std::endl; const char* multibyteString = u8"你好,世界!"; // UTF-8編碼的多字節(jié)字符串 std::cout << "Length of multibyteString: " << std::strlen(multibyteString) << std::endl; const wchar_t* wideString = L"你好,世界!"; // UTF-16編碼的寬字符字符串 std::wcout << L"Length of wideString: " << std::wcslen(wideString) << std::endl; return 0; } /* 運(yùn)行結(jié)果為: Length of charString: 18 Length of multibyteString: 18 Length of wideString: 6 */
注意:下文討論的都是單字節(jié)的流向。
緩沖(buffer)
在 Linux 中,使用 read、write 函數(shù)對(duì)文件描述符進(jìn)行讀寫操作屬于系統(tǒng)調(diào)用,用于直接讀寫磁盤文件。(其實(shí)也不是直接讀寫讀寫磁盤文件,Linux內(nèi)核中會(huì)維護(hù)高速緩沖區(qū)用于提高磁盤文件的讀寫效率,這部分內(nèi)容超出的本文的討論范疇,略過。)而C語言I/O標(biāo)準(zhǔn)庫提供的I/O操作在用戶態(tài),通常帶有緩沖(當(dāng)然,也可以沒有緩沖),使用標(biāo)準(zhǔn)I/O庫提供的I/O操作先將數(shù)據(jù)寫入緩沖中,然后等待某個(gè)條件達(dá)成,在將緩沖中的數(shù)據(jù)寫入磁盤文件(調(diào)用 write 函數(shù))。標(biāo)準(zhǔn)I/O庫提供緩沖的目的是減少 read 和 write 調(diào)用的次數(shù),提高 I/O 效率。(而在實(shí)際的開發(fā)中,I/O緩沖的利用需要根據(jù)實(shí)際的場景來使用,并不是說有了緩沖,I/O效率就提高了。)
標(biāo)準(zhǔn) I/O 提供了三種緩沖類型:
- 全緩沖。對(duì)于寫操作,當(dāng)緩沖區(qū)寫滿后,才將緩沖中的數(shù)據(jù)寫入文件;讀操作同理。對(duì)于寫操作,可以調(diào)用 flush 函數(shù)主動(dòng)將緩沖中的數(shù)據(jù)寫入文件而不論緩沖是否被寫滿。下文將調(diào)用 flush 函數(shù)的操作稱為 ”刷新緩沖“。
- 行緩沖。當(dāng)讀或?qū)憯?shù)據(jù)遇到換行符時(shí),將緩沖中的數(shù)據(jù)進(jìn)行輸入或輸出。行緩沖的一個(gè)限制是:當(dāng)緩沖已滿,即使未遇到換行符,也將其進(jìn)行輸入輸出。
- 不帶緩沖。即I/O操作直接寫入文件。
I/O標(biāo)準(zhǔn)庫中常用的函數(shù)
文件流的打開和關(guān)閉
下面三個(gè)函數(shù)可用于文件流的打開,其中 fopen 最為常用,先重點(diǎn)介紹該函數(shù),剩余兩個(gè)當(dāng)遇到具體的使用場景時(shí)再來補(bǔ)充。在 Linux 中是可以使用 man 命令查看詳情。
#include <stdio.h> /* pathname參數(shù)表示打開的文件路徑名; type參數(shù)指定對(duì)文件流的讀、寫方式。 若打開出錯(cuò),返回 NULL。 */ FILE* fopen(const char* pathname, const char* type); FILE* freopen(const char* pathname, const char* type, FILE* fp); FILE* fdopen(int fd, const char* type);
對(duì)于 fopen
函數(shù),type
參數(shù)的值及表示的讀、寫方式如下所示:
r
或rb
,以讀的方式打開。w
或wb
,以寫的方式打開;若指定文件名存在,則將該文件內(nèi)容清空;不存在,創(chuàng)建新文件。a
或ab
,以追加寫的方式打開文件。若指定文件名不存在,創(chuàng)建新文件。r
或r+b
或rb+
,以讀寫的方式打開文件;指定文件名不存在,則出錯(cuò)。w+
或w+b
或wb+
,以讀寫的方式打開文件;若指定文件名存在,則將該文件內(nèi)容清空;不存在,創(chuàng)建新文件。a+
或a+b
或ab+
,以讀寫的方式打開文件,讀寫操作在文件尾開始進(jìn)行,若指定文件名不存在,創(chuàng)建新文件。
使用字符b作為type的一部分,使得標(biāo)準(zhǔn)I/O系統(tǒng)可以區(qū)分為文本文件和二進(jìn)制文件。UNIX不對(duì)這兩種文件進(jìn)行區(qū)分。
– 《UNIX 高級(jí)環(huán)境編程》
使用 fopen 函數(shù)開打的文件流默認(rèn)是自帶緩沖的,緩沖模式為全緩沖。
fclose
函數(shù)用于關(guān)閉一個(gè)打開的文件流。注意,對(duì)于一個(gè)已經(jīng)關(guān)閉了的文件流調(diào)用 fclose 函數(shù),行為是未定義的。
#include <stdio.h> /* 若成功,返回0;若出錯(cuò),返回 EOF */ int fclose(FILE* fp);
當(dāng)調(diào)用 fclose 函數(shù)或者當(dāng)一個(gè)進(jìn)程正常終止(調(diào)用 exit 函數(shù)或從 main 函數(shù)返回),會(huì)先刷新緩沖。若是使用標(biāo)準(zhǔn)IO默認(rèn)的緩沖,則會(huì)釋放緩沖。
給文件流設(shè)置自定義的緩沖
若希望自己掌控文件流的緩沖,可以自定義一個(gè)緩沖,將其于打開的文件流的進(jìn)行綁定。主要有如下三個(gè)函數(shù)可以綁定自定義的緩沖:
#include <stdio.h> void setbuf(FILE* fp, char* buf); /* buf參數(shù)為指定緩沖區(qū),mode表示緩沖類型,size指定了緩沖的大小。 成功返回0;出錯(cuò)返回非0。 */ void setvbuf(FILE* fp, char* buf, int mode, size_t size); void setbuffer(FILE* fp, char* buf, size_t size);
mode
參數(shù)的可選值如下:
_IOFBF
,全緩沖。_IOLBF
,行緩沖。_IONBF
,無緩沖。
對(duì)上面三個(gè)函數(shù)進(jìn)行如下補(bǔ)充說明:
setbuf
等價(jià)于setvbuf(fp, buf, buf ? _IOFBF : _IONBF, BUFFSIX)
;其中, BUFZSIZE 在標(biāo)準(zhǔn)庫的默認(rèn)值,我的環(huán)境下為 8096。setbuffer
等價(jià)于setvbuf(fp, buf, buf ? _IOFBF : _IONBF, size)
。- 若 buf 參數(shù)為空,則文件流為無緩沖。
在使用文件流的進(jìn)行文件操作時(shí),全緩沖的緩沖模式用得最多,因此推薦使用 setbuffer
,不用費(fèi)力去記緩沖模式的參數(shù)值。
文件流的讀寫
打開文件流后,有三種類型的非格式化I/O操作:
- 每次讀寫一個(gè)字符。一次讀寫一個(gè)字符。
- 每次讀寫一行。一次讀寫一行,每一行以換行符終止。
- 直接讀寫,即指定讀寫的字節(jié)數(shù)。每次IO操作讀寫某種類型的對(duì)象,每個(gè)對(duì)象具有指定的長度。
讀寫一個(gè)字符
對(duì)于讀一個(gè)字符,有如下三個(gè)函數(shù)可供選擇:
#include <stdio.h> int getc(FILE* fp); int fgetc(FILE* fp); int getchar(void); /* 以上三個(gè)函數(shù),成功返回讀取的字符,返回前將 unsigned char 類型轉(zhuǎn)換為 int 類型;若已到達(dá)文件尾端或出錯(cuò),返回 EOF。 */
對(duì)上面三個(gè)函數(shù)進(jìn)行補(bǔ)充說明:
getchar(void)
等價(jià)于getc(stdin)
。- 在《UNIX 環(huán)境高級(jí)編程》中,”
getc
函數(shù)可能被實(shí)現(xiàn)為宏,而fgetc
一定為函數(shù)“。因此推薦使用fget
函數(shù),因?yàn)楹甓x的參數(shù)存在副作用。
對(duì)于上述三個(gè)函數(shù)的出錯(cuò),在文件流 FILE 對(duì)象中,每個(gè) FILE 對(duì)象維護(hù)了兩個(gè)標(biāo)志:
- 出錯(cuò)標(biāo)志;
- 文件結(jié)束標(biāo)志;
可以使用 ferror
和 feof
函數(shù)進(jìn)行檢查:
#include <stdio.h> /* 檢查 fp 指定的流是否發(fā)生了錯(cuò)誤。若為真,則返回非0;否則,返回0 */ int ferror(FILE* fp); /* 檢查 fp 指定的流是否到達(dá)文件尾。若為真,則返回非0;否則,返回0 */ int feof(FILE* fp); /* 清楚上述兩個(gè)標(biāo)志 */ void clearerr(FILE* fp);
在使用文件流進(jìn)行文件操作時(shí),一個(gè)好的編碼習(xí)慣是,使用 ferror
函數(shù)檢測讀寫后的文件流狀態(tài)。
對(duì)于寫一個(gè)字符,有如下三個(gè)函數(shù)可供選擇:
#include <stdio.h> int putc(int c, FILE* fp); int fputc(int c, FILE* fp); // putchar(c) 等價(jià)于 putc(c, stdout); int putchar(int c); /* 以上三個(gè)函數(shù),成功,返回c;若出錯(cuò),返回EOF。 */
和 getc、fgetc 類似,putc 可能實(shí)現(xiàn)為宏,fputc 被定義為一個(gè)函數(shù),因此推薦使用 fputc。
下面給出幾個(gè)編碼示例:
假設(shè)文件中的內(nèi)容為 python
,一個(gè)字符一個(gè)字符的把文件中的內(nèi)容輸出到標(biāo)準(zhǔn)輸出。
FILE* fp = fopen("example.txt", "a+"); int c; while ((c = fgetc(fp)) != EOF) { printf("%c", (unsigned char)c); } fclose(fp); /* 輸出為:python */
假設(shè)文件中的內(nèi)容為 python
,一個(gè)字符一個(gè)字符寫入文件。
FILE* fp = fopen("example.txt", "a+"); char buf[7] = "golang"; for (int i = 0; i < 6; ++i) { fputc(buf[i], fp); } fclose(fp); /* 文件中的內(nèi)容為:pythongolang */
(以上只是簡單的編碼示例,更復(fù)雜的操作可以結(jié)合文件流的定位來進(jìn)行,碰到實(shí)際的場景在來補(bǔ)充。)
讀寫一行
對(duì)于讀一行,有以下兩個(gè)函數(shù)可供選擇:
#include <stdio.h> // n 為指定的緩沖區(qū)大小 // 將 fp 文件中的內(nèi)容寫入 buf 中,直至遇到換行符或者buf寫滿。 char* fgets(char* buf, int n, FILE* fp); // gets 從標(biāo)準(zhǔn)輸入進(jìn)行讀 char* gets(char* buf); /* 以上兩個(gè)函數(shù),若成功,返回buf;若已達(dá)到文件末尾或出錯(cuò),返回NULL。 */
gets 函數(shù)不能指定緩沖區(qū)大小,建議只使用 fgets 函數(shù)。
對(duì)于寫一行,有以下兩個(gè)函數(shù)可供選擇:
#include <stdio.h> // fputs 不會(huì)將換行符寫入到文件流中 int fputs(const char* str, FILE* fp); // puts 將字符串輸出到標(biāo)準(zhǔn)輸出,會(huì)將換行符作為輸出。 int puts(const char* str); /* 以上兩個(gè)函數(shù),若成功,返回非負(fù)責(zé);若出錯(cuò),返回 EOF。 */
建議只是用 fputs 函數(shù)。
直接讀寫
在《UNIX環(huán)境高級(jí)編程》一書中,直接讀寫也即二進(jìn)制讀寫,指一次讀寫一個(gè)完整的結(jié)構(gòu),例如一個(gè)結(jié)構(gòu)體對(duì)象。通常用來讀寫指定的字節(jié)大小的數(shù)據(jù)。
常用的直接讀寫的函數(shù)如下:
#include <stdio.h> // 若出錯(cuò)或到達(dá)文件末尾,返回值可以小于 nobj;需要調(diào)用 ferror 或 feof 來判斷是哪一種情況。 size_t fread(void* ptr, size_t size, size_t nobj, FILE* fp); // 若出錯(cuò),返回值小于 nobj size_t fwrite(const void* ptr, size_t size, size_t nobj, FILE* fp); /* 以上兩個(gè)函數(shù),返回讀寫的對(duì)象數(shù)量。 ptr 指向待寫入的對(duì)象 size 表示對(duì)象的大小 nobj 表示寫入的對(duì)象數(shù)量 */
下面給出幾個(gè)編碼示例:
讀寫char數(shù)組形式的字符串。
char buffer[16] = "python"; FILE* wfp = fopen("test2.txt", "w"); fwrite(buffer, 1, strlen(buffer), wfp); fclose(wfp); printf("%s\n", buffer); char buffer2[16]; FILE *rfp = fopen("test2.txt", "r"); fread(buffer2, 1, strlen(buffer), rfp); fclose(rfp); printf("%s\n", buffer2);
使用 fread 和 fwrite 讀寫一個(gè)類的示例對(duì)象(有bug)。(無意中測試出來的一個(gè)bug,暫未解決。一個(gè)初步的思路為,需要去學(xué)習(xí)了解 C++ 的對(duì)象模型,即一個(gè)C++的對(duì)象在內(nèi)存中是如何布局的,然后在深入 fread 和 fwrite 的源碼中,去了解,其底層是如何讀寫的。在此文中先留個(gè)坑,后面再來填補(bǔ))
void test1() { Person p1("Jack", 25); FILE* wfp = fopen("Person", "wb"); fwrite((void*)&p1, sizeof(Person), 1, wfp); fclose(wfp); Person* p2 = new Person("Lisa", 19); std::cout << p2->name() << std::endl; FILE* rfp = fopen("Person", "rb"); fread(p2, sizeof(Person), 1, rfp); fclose(rfp); std::cout << p2->name() << std::endl; // delete p2; /* 若在這行執(zhí)行此語句,出現(xiàn) Segmentation fault */ Person p3; FILE* rfp2 = fopen("Person", "rb+"); fread(&p3, sizeof(Person), 1, rfp2); fclose(rfp2); std::cout << p3.name() << std::endl; /* 程序結(jié)束,Segmentation fault */ }
格式化讀寫
格式化輸出常用的有 printf
, fprintf
, dprintf
, sprintf
, snprintf
五個(gè)函數(shù),下面介紹最常用的 printf
和 fprintf
函數(shù)。
#include <stdio.h> int printf(const char* format, ...); int fprintf(FILE* fp, const char* format, ...); /* 以上兩個(gè)函數(shù),成功,返回輸出的字符數(shù);出錯(cuò),返回復(fù)制。 format 表示格式化字符串; ... 為C語言的可變參數(shù),需要與 format 中的格式化進(jìn)行匹配。 */
格式化輸入常用的有如 scanf
, fscanf
和 sscanf
。
對(duì)于標(biāo)準(zhǔn)庫中格式化讀寫的更多細(xì)節(jié),太過瑣碎,可參考:https://en.cppreference.com/w/cpp/io/c/fscanf
多線程的安全性
上述的章節(jié)中介紹的文件流的讀寫函數(shù)都是線程安全的,會(huì)在正常進(jìn)行磁盤文件讀寫時(shí)進(jìn)行加鎖操作。標(biāo)準(zhǔn)庫中也提供非線程安全的版本,它們都以 _unlocked
后綴結(jié)尾。例如對(duì)于 fread 和 fwrite 的非線程安全版本為 fread_unlocked 和 fwrite_unlocked。
總結(jié)
到此這篇關(guān)于Linux系統(tǒng)下C語言中的標(biāo)準(zhǔn)IO的文章就介紹到這了,更多相關(guān)C語言標(biāo)準(zhǔn)IO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++?error:crosses?initialization?of問題解決分析
這篇文章主要介紹了c++?error:crosses?initialization?ofde?問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08C語言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解
這篇文章主要介紹了C語言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01C++實(shí)現(xiàn)編碼轉(zhuǎn)換的示例代碼
這篇文章主要介紹了C++實(shí)現(xiàn)編碼轉(zhuǎn)換的示例代碼,幫助大家快捷的實(shí)現(xiàn)編碼轉(zhuǎn)換,感興趣的朋友可以了解下2020-08-08