4組C語(yǔ)言中順序讀寫(xiě)文件的函數(shù)分享
預(yù)備知識(shí):fopen和fclose
如果我們要讀寫(xiě)一個(gè)文件,就必須先打開(kāi)這個(gè)文件,讀寫(xiě)完后,還需要關(guān)閉這個(gè)文件。這就像,你要喝一杯水,需要先打開(kāi)杯蓋,才能喝水,喝完水后還需要把蓋子蓋上。
打開(kāi)文件的原理是,打開(kāi)文件后,在內(nèi)存中創(chuàng)建一個(gè)FILE類型的變量,用來(lái)記錄打開(kāi)的文件的相關(guān)信息。FILE類型是一個(gè)結(jié)構(gòu)體類型。
關(guān)閉文件的原理是,根據(jù)這個(gè)FILE類型的變量里描述的文件信息,通過(guò)一定手段把文件關(guān)閉。
在學(xué)習(xí)C語(yǔ)言的過(guò)程中,我們不需要知道具體的細(xì)節(jié),會(huì)用就行了。C語(yǔ)言中打開(kāi)文件需要使用函數(shù)fopen,關(guān)閉文件需要使用函數(shù)fclose。
fopen的聲明如下:
FILE * fopen ( const char * filename, const char * mode );
看這個(gè)聲明,可以了解到,第一個(gè)參數(shù)就是要打開(kāi)文件的文件名,第二個(gè)參數(shù)是用什么方式打開(kāi)(讀?寫(xiě)?還是其他模式?)。函數(shù)會(huì)打開(kāi)這個(gè)文件,在內(nèi)存中創(chuàng)建一個(gè)相對(duì)應(yīng)的文件信息區(qū),其實(shí)就是創(chuàng)建一個(gè)FILE類型的變量,這個(gè)變量記錄了文件的相關(guān)信息。接著,這個(gè)函數(shù)會(huì)返回這個(gè)FILE變量的地址,如果函數(shù)打開(kāi)文件失敗會(huì)返回NULL指針。
這里為了簡(jiǎn)單起見(jiàn),都在工程目錄下操作文件,所以文件名不用帶上路徑。如果要在其他位置操作文件,根據(jù)具體情況帶上絕對(duì)路徑或者相對(duì)路徑就可以了。
假設(shè)我們要操作的文件的文件名是test.txt,我們想要寫(xiě)這個(gè)文件(寫(xiě)文件的模式是"w",及write的簡(jiǎn)寫(xiě)),可以這么調(diào)用這個(gè)函數(shù):
FILE* pf = fopen("test.txt", "w");
這里的2個(gè)參數(shù)都用雙引號(hào)引起,因?yàn)槭亲址?。返回值需要使用一個(gè)FILE*的指針來(lái)接收。和malloc類似,需要檢查返回值是否為NULL指針,如果為NULL指針,則打開(kāi)文件失敗,需要進(jìn)行錯(cuò)誤處理,舉個(gè)例子:
if (pf == NULL) { perror("fopen"); exit(1); }
以上的代碼中,當(dāng)檢查到pf為NULL,此時(shí)打開(kāi)文件失敗,用perror報(bào)個(gè)錯(cuò),再exit掉,終止進(jìn)程。
當(dāng)然,操作文件不只有"w"一種模式。本篇博客主要介紹比較常見(jiàn)的4種模式,分別是:
- “w” - 寫(xiě)文件,即write的簡(jiǎn)寫(xiě)。
- “r” - 讀文件,即read的簡(jiǎn)寫(xiě)。
- “wb” - 通過(guò)二進(jìn)制的方式寫(xiě)文件,b是binary的縮寫(xiě)。
- “rb” - 通過(guò)二進(jìn)制的方式讀文件。
fclose函數(shù)的聲明如下:
int fclose ( FILE * stream );
具體的使用很簡(jiǎn)單,前面我們用一個(gè)FILE*的指針來(lái)接收f(shuō)open函數(shù)的返回值,只需要把這個(gè)指針傳給fclose就能關(guān)閉對(duì)應(yīng)的文件了。和free函數(shù)類似,fclose函數(shù)沒(méi)有能力把傳給它的指針置為NULL,為了防止野指針,需要程序員手動(dòng)置為NULL值。
fclose(pf); pf = NULL;
1.字符讀寫(xiě):fputc和fgetc
fputc用于向文件中寫(xiě)入一個(gè)字符。
讀寫(xiě)文件前,應(yīng)該打開(kāi)文件。這次以"w"的模式打開(kāi)。
FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); exit(1); }
接下來(lái)是使用fputc寫(xiě)文件的操作。寫(xiě)完文件后,需要關(guān)閉文件。
fclose(pf); pf = NULL;
后面的函數(shù)都是按照打開(kāi)文件->讀寫(xiě)文件->關(guān)閉文件的順序,唯一的區(qū)別是打開(kāi)文件的方式不一樣,也就是fopen的第二個(gè)參數(shù)不一樣。
fputc的聲明如下:
int fputc ( int character, FILE * stream );
很明顯,第一個(gè)參數(shù)表示寫(xiě)入的字符,第二個(gè)參數(shù)表示指向文件信息區(qū)的文件指針。比如,如果我要把字符’a’寫(xiě)到pf對(duì)應(yīng)的文件里,應(yīng)該這么寫(xiě):
fputc('a', pf);
舉一反三:如果要把小寫(xiě)的a~z,總共26個(gè)字母寫(xiě)到文件中,應(yīng)該如何寫(xiě)呢?
for (int ch = 'a'; ch <= 'z'; ch++) { fputc(ch, pf); }
程序執(zhí)行的結(jié)果是:創(chuàng)建了一個(gè)test.txt文件,并寫(xiě)入了a~z。
接下來(lái),我們來(lái)讀一下這個(gè)文件。使用fgetc之前也需要打開(kāi)文件,打開(kāi)的模式是"r",使用完后需要關(guān)閉文件。
fgetc是用來(lái)讀取一個(gè)字符的。聲明如下:
int fgetc ( FILE * stream );
函數(shù)會(huì)讀一個(gè)字符然后返回這個(gè)字符的ASCII碼值。如果讀取失敗會(huì)返回EOF。
如果想讀取剛剛寫(xiě)完的文件,把26個(gè)字母讀出來(lái),可以使用循環(huán)讀26次,但是如果不知道文件中有多少字符呢?那就一直讀取,直到讀完為止。那如何判斷讀取結(jié)束呢?當(dāng)fgetc返回EOF的時(shí)候讀取就結(jié)束了。
int ch = 0; while ((ch = fgetc(pf)) != EOF) { printf("%c ", ch); }
運(yùn)行結(jié)果如下:
2.文本行讀寫(xiě):fputs和fgets
使用fputs之前,要以"w"的模式打開(kāi)文件,使用完后要關(guān)閉文件,操作同上。
fputs是用來(lái)寫(xiě)入文本行的,聲明如下:
int fputs ( const char * str, FILE * stream );
str表示要寫(xiě)入的字符串。比如,我寫(xiě)10行Hello, world!!!進(jìn)去,每寫(xiě)一個(gè)就換個(gè)行。
for (int i = 0; i < 10; i++) { fputs("Hello, world!!!\n", pf); }
可以發(fā)現(xiàn),test.txt中就有了10行Hello, world!!!。
想把它們讀出來(lái),可以使用fgets,打開(kāi)文件的模式是"r"。聲明如下:
char * fgets ( char * str, int num, FILE * stream );
str表示你想把讀到的字符串存在哪,num表示存儲(chǔ)的空間有多大(最多存多少個(gè)字符,包括字符串結(jié)尾的’\0’)。函數(shù)會(huì)把str返回回來(lái),如果讀取失敗,會(huì)返回NULL指針。所以讀取時(shí),可以使用循環(huán),通過(guò)每次判斷返回值是否為NULL指針,來(lái)判斷是否讀取結(jié)束。
char str[256] = {0}; while (fgets(str, 256, pf)) { puts(str); }
輸出結(jié)果:
由于寫(xiě)入時(shí)每個(gè)Hello, world!!!后面都寫(xiě)了個(gè)’\n’,而puts會(huì)在打印完字符串后也換個(gè)行,所以相當(dāng)于每次打印完Hello, world!!!后面都換2次行。如果你只想換一次行,可以使用printf來(lái)實(shí)現(xiàn):
char str[256] = { 0 }; while (fgets(str, 256, pf)) { printf("%s", str); }
輸出結(jié)果如下:
3.格式化讀寫(xiě):fprintf和fscanf
fprintf和fscanf,與printf和scanf非常像,唯一的區(qū)別就是,fprintf和fscanf前面多了個(gè)f。(emmm,聽(tīng)君一席話,如聽(tīng)一席話)
在學(xué)習(xí)這兩個(gè)函數(shù)之前,建議先學(xué)一下sprintf和sscanf這兩個(gè)函數(shù),點(diǎn)這里
我還是采取同樣的講解思路。你也許不知道fprintf和sscanf,但你一定知道printf和scanf(別告訴我你不知道)。
先說(shuō)printf。舉個(gè)例子,假設(shè)有一個(gè)結(jié)構(gòu)體:
struct S { int i; double d; char arr[30]; };
我創(chuàng)建了一個(gè)結(jié)構(gòu)體變量:
struct S s = {10, 3.14, "abcdef"};
我想你把這個(gè)結(jié)構(gòu)體的數(shù)據(jù)用printf打印到屏幕上,你會(huì)怎么寫(xiě)?
printf("%d %lf %s\n", s.i, s.d, s.arr);
如果這些數(shù)據(jù)不是打印到屏幕上,而是“打印”到文件中,只需要在函數(shù)名前面加個(gè)f,所有參數(shù)最前面加個(gè)pf,就行了。
fprintf(pf, "%d %lf %s\n", s.i, s.d, s.arr);
簡(jiǎn)單吧?注意fprintf需要的打開(kāi)文件方式是"w",使用完后需要關(guān)閉文件。來(lái)看看此時(shí)的test.txt文件:
干得漂亮!接下來(lái)來(lái)看看fscanf。fscanf使用前需要使用"r"模式打開(kāi)文件,使用完后需要關(guān)閉文件,都學(xué)到這了,別忘了!(稍稍總結(jié)一下,目前所有寫(xiě)文件操作打開(kāi)文件的模式都是"w",即write的簡(jiǎn)寫(xiě),而讀文件操作打開(kāi)文件的模式都是"r",即read的縮寫(xiě)。)
還是那句話,你也許沒(méi)有聽(tīng)說(shuō)過(guò)fscanf,但你一定直到scanf。假設(shè)我創(chuàng)建了一個(gè)結(jié)構(gòu)體變量:
struct S s = {0};
我想你用scanf函數(shù),實(shí)現(xiàn)從鍵盤中輸入數(shù)據(jù)到s中,你會(huì)怎么寫(xiě)?
scanf("%d %lf %s", &s.i, &s.d, s.arr);
如果不是從鍵盤中輸入數(shù)據(jù),而是從文件中讀取數(shù)據(jù),只需要在函數(shù)名前加個(gè)f,參數(shù)最前面加個(gè)pf就行了。
fscanf(pf, "%d %lf %s", &s.i, &s.d, s.arr);
讀完之后,我們可以把s中的數(shù)據(jù)打印出來(lái)。
printf("%d %lf %s\n", s.i, s.d, s.arr);
看吧,讀取成功了。
4.二進(jìn)制讀寫(xiě):fwrite和fread
前面我們都是讀寫(xiě)字符文件,也就是以字符的形式讀寫(xiě)文件,這是我們能看的懂的形式。但是在計(jì)算機(jī)的世界中,都是二進(jìn)制的,所以我們還需要學(xué)習(xí)用二進(jìn)制的方式來(lái)讀寫(xiě)文件。
先來(lái)學(xué)習(xí)下fwrite。由于是二進(jìn)制的形式讀寫(xiě)文件,打開(kāi)文件的模式是"wb",b代表二進(jìn)制。函數(shù)的聲明如下:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
fwrite函數(shù)和fread函數(shù)的參數(shù)列表是一樣的,所以學(xué)會(huì)一個(gè)就相當(dāng)于兩個(gè)都學(xué)會(huì)了。第一個(gè)參數(shù)表示你要寫(xiě)入的數(shù)據(jù)在內(nèi)存中存儲(chǔ)的位置,第二個(gè)參數(shù)表示寫(xiě)入一個(gè)數(shù)據(jù)的大小,第三個(gè)參數(shù)表示要寫(xiě)入幾個(gè)數(shù)據(jù)。
比如,我有一個(gè)結(jié)構(gòu)體變量:
struct S s = {10, 3.14, "abcdef"};
我想把它以二進(jìn)制的形式寫(xiě)到內(nèi)存中,第一個(gè)參數(shù)就是s的地址,第二個(gè)參數(shù)就是一個(gè)結(jié)構(gòu)體的大小,由于我只想寫(xiě)1個(gè)s進(jìn)去,所以第三個(gè)參數(shù)就是1。
fwrite(&s, sizeof(s), 1, pf);
輸出結(jié)果:
看不懂呀!很正常,因?yàn)檫@玩意是二進(jìn)制。接下來(lái)我們用二進(jìn)制的方式來(lái)把數(shù)據(jù)重新讀出來(lái)。
fread是用來(lái)二進(jìn)制的讀文件的,打開(kāi)文件的模式是"rb",使用完后需要關(guān)閉文件。函數(shù)的聲明如下:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
第一個(gè)參數(shù)表示要把讀到的數(shù)據(jù)存到哪,第二個(gè)參數(shù)表示讀取一個(gè)數(shù)據(jù)的大小,第三個(gè)參數(shù)表示要讀幾個(gè)數(shù)據(jù)。
比如,我們先創(chuàng)建一個(gè)結(jié)構(gòu)體:
struct S s = {0};
接著把讀到的數(shù)據(jù)放到s里,有了前面fwrite的經(jīng)驗(yàn),寫(xiě)起來(lái)就簡(jiǎn)單了。
fread(&s, sizeof(s), 1, pf);
接著把s中的數(shù)據(jù)打印出來(lái):
printf("%d %lf %s\n", s.i, s.d, s.arr);
輸出結(jié)果如下:
5.格局打開(kāi)
其實(shí),我們可以把“屏幕”和“鍵盤”也當(dāng)成文件。用stdout表示屏幕,stdin表示鍵盤,舉個(gè)例子:
fprintf(stdout, "%d %lf %s\n", s.i, s.d, s.arr);
這行代碼會(huì)把結(jié)構(gòu)體s中的數(shù)據(jù)寫(xiě)入到stdout這個(gè)“文件”中,而stdout表示屏幕,所以也就把結(jié)構(gòu)體中的數(shù)據(jù)打印到屏幕上了!哈哈哈,有意思吧。
其實(shí),stdout是標(biāo)準(zhǔn)輸出流,而stdin是標(biāo)準(zhǔn)輸入流。在前面的函數(shù)中,只要是以"w"模式打開(kāi)文件才能使用的函數(shù)(fputc, fputs, fprintf)的FILE*類型的參數(shù)就可以傳stdout,而以"r"模式打開(kāi)文件才能使用的函數(shù)(fgetc, fgets, fscanf)的FILE*類型的參數(shù)就可以傳stdin,分別表示“寫(xiě)屏幕”(把數(shù)據(jù)打印到屏幕上)和“讀鍵盤”(從鍵盤中輸入數(shù)據(jù))。
你可能納悶了,為啥stdout和stdin就不用有fopen和fclose這樣的操作呢?這是因?yàn)?,只要一個(gè)C程序跑起來(lái),就默認(rèn)打開(kāi)了三個(gè)流,分別是:stdout(標(biāo)準(zhǔn)輸出流),stdin(標(biāo)準(zhǔn)輸入流)和stderr(標(biāo)準(zhǔn)錯(cuò)誤流),所以不需要我們手動(dòng)打開(kāi)。
這時(shí),你再想想,printf和fprintf有什么區(qū)別?區(qū)別就是,printf只能格式化輸出標(biāo)準(zhǔn)輸出流的數(shù)據(jù),而fprintf可以格式化輸出任意輸出流的數(shù)據(jù)(包括標(biāo)準(zhǔn)輸出流和文件流);同理,scanf和fscanf的區(qū)別是,scanf只能格式化輸入標(biāo)準(zhǔn)輸入流中的數(shù)據(jù),而fscanf可以輸入任意輸入流的數(shù)據(jù)(包括標(biāo)準(zhǔn)輸入流和文件流)。那sprintf和sscanf和它們之間有什么區(qū)別呢?這兩個(gè)函數(shù)是用來(lái)進(jìn)行格式化數(shù)據(jù)和字符串的相互轉(zhuǎn)換的,就跟這些“流”沒(méi)什么關(guān)系了。
到此這篇關(guān)于4組C語(yǔ)言中順序讀寫(xiě)文件的函數(shù)分享的文章就介紹到這了,更多相關(guān)C語(yǔ)言順序讀寫(xiě)文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言詳細(xì)實(shí)現(xiàn)猜拳游戲流程
在學(xué)習(xí)了循環(huán)、分支、和函數(shù)之后,可以寫(xiě)一些簡(jiǎn)單的小游戲來(lái)給自己的編程之路增添一份樂(lè)趣。不僅提升了編碼能力,還可以邊學(xué)邊玩,簡(jiǎn)直妙哉妙哉2022-05-05C語(yǔ)言實(shí)現(xiàn)萬(wàn)年歷效果
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)萬(wàn)年歷效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11