C語言文件操作詳解以及詳細步驟
一、為什么使用文件?
當(dāng)我們在編寫一個項目的時候,自然而然想到要把之前寫入的數(shù)據(jù)保存起來。而只有我們自己選擇刪除數(shù)據(jù)的時候,數(shù)據(jù)才不復(fù)存在。這就涉及到了數(shù)據(jù)持久化的問題,我們一般數(shù)據(jù)持久化的方法有,把數(shù)據(jù)存放在磁盤文件、存放到數(shù)據(jù)庫等方式。此處我們就講到如何將數(shù)據(jù)放入到磁盤文件當(dāng)中。
二、什么是文件?
磁盤上的文件就是文件。例如電腦當(dāng)中的C盤內(nèi)放入的文件夾內(nèi)的內(nèi)容就是文件。但是在程序設(shè)計中,我們一般談的文件有兩種:程序文件、數(shù)據(jù)文件(從文件功能的角度來分類的)。
1.程序文件
包括源程序文件(后綴為.c),目標文件(windows環(huán)境后綴為.obj),可執(zhí)行程序(windows環(huán)境后綴為.exe)。
2.數(shù)據(jù)文件
文件的內(nèi)容不一定是程序,而是程序運行時讀寫的數(shù)據(jù),比如程序運行需要從中讀取數(shù)據(jù)的文件,或者輸出內(nèi)容的文件。
此篇博客討論的大部分都是數(shù)據(jù)文件。因為我們要學(xué)會如何將文件中的數(shù)據(jù)輸入到內(nèi)存中和如何將程序中的數(shù)據(jù)輸出到文件當(dāng)中。在以前各章所處理數(shù)據(jù)的輸入輸出都是以終端為對象的,即從終端的鍵盤輸入數(shù)據(jù),運行結(jié)果顯示到顯示器上。其實有時候我們會把信息輸出到磁盤上,當(dāng)需要的時候再從磁盤上把數(shù)據(jù)讀取到內(nèi)存中使用,這里處理的就是磁盤上文件。
3.文件名
一個文件要有一個唯一的文件標識,以便用戶識別和引用。
文件名包含3部分:文件路徑+文件名主干+文件后綴
例如: c:\code\test.txt
為了方便起見,文件標識常被稱為文件名。
三、文件的打開和關(guān)閉
1.文件指針
緩沖文件系統(tǒng)中,關(guān)鍵的概念是“文件類型指針”,簡稱**“文件指針”**。
每個被使用的文件都在內(nèi)存中開辟了一個相應(yīng)的文件信息區(qū),用來存放文件的相關(guān)信息(如文件的名字,文件狀態(tài)及文件當(dāng)前的位置等)。這些信息是保存在一個結(jié)構(gòu)體變量中的。該結(jié)構(gòu)體類型是有系統(tǒng)聲明的,取名FILE。
例如,VS2013編譯環(huán)境提供的 stdio.h 頭文件中有以下的文件類型申明:
struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }; typedef struct _iobuf FILE; FILE* pf;//文件指針變量
不同的C編譯器的FILE類型包含的內(nèi)容不完全相同,但是大同小異。
每當(dāng)打開一個文件的時候,系統(tǒng)會根據(jù)文件的情況自動創(chuàng)建一個FILE結(jié)構(gòu)的變量,并填充其中的信息,使用者不必關(guān)心細節(jié)。
一般都是通過一個FILE的指針來維護這個FILE結(jié)構(gòu)的變量,這樣使用起來更加方便。
下面我們可以創(chuàng)建一個FILE*的指針變量:
FILE* pf;//文件指針變量
定義pf是一個指向FILE類型數(shù)據(jù)的指針變量??梢允筽f指向某個文件的文件信息區(qū)(是一個結(jié)構(gòu)體變量)。通過該文件信息區(qū)中的信息就能夠訪問該文件。也就是說,通過文件指針變量能夠找到與它關(guān)聯(lián)的文件。
2.文件的打開和關(guān)閉
文件在讀寫之前應(yīng)該先打開文件,在使用結(jié)束之后應(yīng)該關(guān)閉文件。
在編寫程序的時候,在打開文件的同時,都會返回一個FILE*的指針變量指向該文件,也相當(dāng)于建立了指針和文件的關(guān)系。
ANSIC 規(guī)定使用fopen函數(shù)來打開文件,fclose來關(guān)閉文件。
要記住的是當(dāng)打開文件后對數(shù)據(jù)進行處理完一定要關(guān)閉文件,否則可能會造成數(shù)據(jù)的丟失。
//打開文件 FILE * fopen ( const char * filename, const char * mode ); //關(guān)閉文件 int fclose ( FILE * stream );
對于文件的寫入和讀取方式,重點掌握以下幾種即可。
文件使用方式 | 含義 | 如果指定文件不存在 |
---|---|---|
“r”(只讀) | 為了輸入數(shù)據(jù),打開一個已經(jīng)存在的文本文件 | 出錯 |
“w”(只寫) | 為了輸出數(shù)據(jù),打開一個文本文件 | 建立一個新的文件 |
“a”(追加) | 向文本文件尾添加數(shù)據(jù) | 建立一個新的文件 |
“rb”(只讀) | 為了輸入數(shù)據(jù),打開一個二進制文件 | 出錯 |
“wb”(只寫) | 為了輸出數(shù)據(jù),打開一個二進制文件 | 建立一個新的文件 |
實例代碼:
/* fopen fclose example */ #include <stdio.h> int main () { FILE * pFile; //打開文件 pFile = fopen ("myfile.txt","w");//以輸出的形式(寫)打開文件 //文件操作 if (pFile!=NULL) { fputs ("fopen example",pFile);//以字符串的形式寫入 //關(guān)閉文件 fclose (pFile); } return 0; }
3.文件的順序讀寫
文件的輸出/寫入就是將數(shù)據(jù)寫入到文件當(dāng)中,而文件的輸入/讀取就是將文件中的內(nèi)容讀取到內(nèi)存當(dāng)中。
以下的對于文件的讀寫方式的函數(shù)均要求掌握
功能 | 函數(shù)名 | 適用于 |
---|---|---|
字符輸入函數(shù) | fgetc | 所有輸入流 |
字符輸出函數(shù) | fputc | 所有輸出流 |
文本行輸入函數(shù) | fgets | 所有輸入流 |
文本行輸出函數(shù) | fputs | 所有輸出流 |
格式化輸入函數(shù) | fscanf | 所有輸入流 |
格式化輸出函數(shù) | fprintf | 所有輸出流 |
二進制輸入 | fread | 文件 |
二進制輸出 | fwrite | 文件 |
四、fseek函數(shù)
根據(jù)文件指針的位置和偏移量來定位文件指針。文件指針顧名思義也是一個指針,它能指向一個字符串中的某個位置。它要接收的參數(shù)有:
第一個參數(shù)是文件指針的名字(流),第二個參數(shù)是文件指針向后偏移數(shù),第三個參數(shù)是fseek函數(shù)中規(guī)定的三個選項之中的其一。
這三項中第一項是SEEK_CUR,即當(dāng)前文件指針的偏移處開始向后偏移。第二項是SEEK_END,即從文件的最末尾處開始向前偏移,當(dāng)然在偏移數(shù)一定要為負數(shù)才能讀取文件中的內(nèi)容。第三項是SEEK_SET,即從文件的最前端處開始向后偏移。舉個例子:
#include <stdio.h> int main () { FILE * pFile; pFile = fopen ( "example.txt" , "wb" ); fputs ( "This is an apple." , pFile ); fseek ( pFile , 9 , SEEK_SET ); fputs ( " sam" , pFile ); fclose ( pFile ); return 0; }
為什么最后在記事本中打印出的結(jié)果是This is a sample.呢?原因是在第一次fputs中是把This is an apple.先放入記事本當(dāng)中,當(dāng)調(diào)用fseek函數(shù)時,從當(dāng)前的文件指針處向后偏移9個字節(jié),文件指針一開始默認指向的是文件的首地址處。因此向后偏移9個字節(jié)后(偏移一個字節(jié)包括空格)指向的是最后一個空格的地址處。而第二次fputs函數(shù)是將“ sam”這個內(nèi)容在上次文件指針指向的地址處開始寫入。因此最后程序運行的結(jié)果如圖:
五、ftell函數(shù)
返回文件指針相對于起始位置的偏移量。
這個函數(shù)比較簡單,輸入的參數(shù)為文件指針流,而返回值的類型為int,即返回的是文件指針所指向的偏移量處。
#include <stdio.h> int main () { FILE * pFile; long size; pFile = fopen ("myfile.txt","rb"); if (pFile==NULL) perror ("Error opening file"); else { fseek (pFile, 0, SEEK_END); // non-portable size=ftell (pFile); fclose (pFile); printf ("Size of myfile.txt: %ld bytes.\n",size); } return 0; }
因為是從文件內(nèi)容的最末尾處開始相對于起始位置的偏移量。則結(jié)果為17。
代碼運行結(jié)果為:
六、rewind函數(shù)
讓文件指針的位置回到文件的起始位置。
rewind函數(shù)的返回值類型為void型,它所需要的參數(shù)是文件指針流。這個函數(shù)相對來說也比較簡單,我們直接舉例子。
#include <stdio.h> int main () { int n; FILE * pFile; char buffer [27]; pFile = fopen ("myfile.txt","w+"); for ( n='A' ; n<='Z' ; n++) fputc ( n, pFile); rewind (pFile); fread (buffer,1,26,pFile); fclose (pFile); buffer[26]='\0'; puts (buffer); return 0; }
代碼運行結(jié)果:
并且在程序的文件夾中有此內(nèi)容的記事本產(chǎn)生:
七、文本文件和二進制文件
根據(jù)數(shù)據(jù)的組織形式,數(shù)據(jù)文件被稱為文本文件或者二進制文件。
數(shù)據(jù)在內(nèi)存中以二進制的形式存儲,如果不加轉(zhuǎn)換的輸出到外存,就是二進制文件。
如果要求在外存上以ASCII碼的形式存儲,則需要在存儲前轉(zhuǎn)換。以ASCII字符的形式存儲的文件就是文本文件。(如整數(shù)10000,需要以ASCII碼輸出到磁盤上,則在磁盤中的存儲形式就是10000)。
如有整數(shù)10000,如果以ASCII碼的形式輸出到磁盤,則磁盤中占用5個字節(jié)(每個字符一個字節(jié)),而二進制形式輸出,則在磁盤上只占4個字節(jié)(VS2013測試)。
再用整數(shù)10000舉例。如果以二進制的形式輸出到磁盤上,則在磁盤上是以二進制的形式存儲。但是我們到文件底下去看二進制形式的文本時,都是亂碼無法看懂(但機器能夠看懂)。此時我們再將該文本文件移到編譯器(VS2019)中。而編譯器內(nèi)有一個二進制編輯器能夠?qū)⒃搧y碼翻譯為二進制數(shù)顯示出來。詳細步驟如下:
代碼:
#include <stdio.h> int main() { int a = 10000; FILE* pf = fopen("test.txt", "wb"); fwrite(&a, 4, 1, pf);//二進制的形式寫到文件中 fclose(pf); pf = NULL; return 0; }
到文件底下去查看文本:
將該文本移到編譯器中后按照以下圖例操作:
此時我們在編譯器中打開該文本:
是什么原因讓10000用二進制的形式存儲變?yōu)榱?0 27 00 00呢?原因是我們先將10000的二進制序列寫出來,為:00000000 00000000 00100111 00010000
,每四位則為一個16進制數(shù)字。則結(jié)果為00 00 27 10,但是我們的編譯器是以小端的形式存儲的。即數(shù)據(jù)的低位存儲到內(nèi)存的低地址中,數(shù)據(jù)的高位存儲到高地址中。則存儲的形式就為:10 27 00 00 。
八、文件讀取結(jié)束的判定
1.feof函數(shù)的錯誤使用
在文件讀取過程中,不能用feof函數(shù)的返回值直接用來判斷文件的是否結(jié)束。
而是應(yīng)用于當(dāng)文件讀取結(jié)束的時候,判斷是讀取失敗結(jié)束,還是遇到文件尾結(jié)束。(feof函數(shù)是判斷結(jié)束過程而不是判斷結(jié)束的結(jié)果)
1.文本文件讀取是否結(jié)束,判斷返回值是否為 EOF(getc)或者NULL(fgets)
例如:
fgetc 判斷是否為 EOF .
fgets 判斷返回值是否為 NULL.
2. 二進制文件的讀取結(jié)束判斷,判斷返回值是否小于實際要讀的個數(shù)。
例如:
fread判斷返回值是否小于實際要讀的個數(shù)。
文件文本中正確使用feof函數(shù)的例子:
#include <stdio.h> #include <stdlib.h> int main(void) { int c; // 注意:int,非char,要求處理EOF FILE* fp = fopen("test.txt", "r"); if(!fp) { perror("File opening failed"); return EXIT_FAILURE; } //fgetc 當(dāng)讀取失敗的時候或者遇到文件結(jié)束的時候,都會返回EOF while ((c = fgetc(fp)) != EOF) // 標準C I/O讀取文件循環(huán) { putchar(c); } //判斷是什么原因結(jié)束的 if (ferror(fp)) puts("I/O error when reading"); else if (feof(fp)) puts("End of file reached successfully"); fclose(fp); }
二進制文件中正確使用feof函數(shù)的例子:
#include <stdio.h> enum { SIZE = 5 }; int main(void) { double a[SIZE] = {1.,2.,3.,4.,5.}; FILE *fp = fopen("test.bin", "wb"); // 必須用二進制模式 fwrite(a, sizeof *a, SIZE, fp); // 寫 double 的數(shù)組 fclose(fp); double b[SIZE]; fp = fopen("test.bin","rb"); size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 讀 double 的數(shù)組 if(ret_code == SIZE) { puts("Array read successfully, contents: "); for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]); putchar('\n'); } else { // error handling if (feof(fp)) printf("Error reading test.bin: unexpected end of file\n"); else if (ferror(fp)) { perror("Error reading test.bin"); } } fclose(fp); }
九、文件緩沖區(qū)
說到文件緩沖區(qū),我們就自然而然想到輸入緩沖區(qū),即當(dāng)一個字符一個字符從鍵盤上輸入時,并不是直接輸入到磁盤內(nèi),而是先放到輸入緩沖區(qū),而當(dāng)輸入緩沖區(qū)內(nèi)的字符放滿后,文件緩沖區(qū)才向磁盤內(nèi)輸入字符。
文件緩沖區(qū)也是一樣的道理。從內(nèi)存向磁盤輸出數(shù)據(jù)會先送到內(nèi)存中的緩沖區(qū),裝滿緩沖區(qū)后才一起送到磁盤上。如果從磁盤向計算機讀入數(shù)據(jù),則從磁盤文件中讀取數(shù)據(jù)輸入到內(nèi)存緩沖區(qū)(充滿緩沖區(qū)),然后再從緩沖區(qū)逐個地將數(shù)據(jù)送到程序數(shù)據(jù)區(qū)(程序變量等)。緩沖區(qū)的大小根據(jù)C編譯系統(tǒng)決定的。
測試代碼:
#include <stdio.h> #include <windows.h> //VS2013 WIN10環(huán)境測試 int main() { FILE*pf = fopen("test.txt", "w"); fputs("abcdef", pf);//先將代碼放在輸出緩沖區(qū) printf("睡眠10秒-已經(jīng)寫數(shù)據(jù)了,打開test.txt文件,發(fā)現(xiàn)文件沒有內(nèi)容\n"); Sleep(10000); printf("刷新緩沖區(qū)\n"); fflush(pf);//刷新緩沖區(qū)時,才將輸出緩沖區(qū)的數(shù)據(jù)寫到文件(磁盤) //注:fflush 在高版本的VS上不能使用了 printf("再睡眠10秒-此時,再次打開test.txt文件,文件有內(nèi)容了\n"); Sleep(10000); fclose(pf); //注:fclose在關(guān)閉文件的時候,也會刷新緩沖區(qū) pf = NULL; return 0; }
我們可以測試一下這個代碼,在程序第一個到fgets函數(shù)處時,立刻去打開test.txt文本文件,我們會發(fā)現(xiàn)里面沒有內(nèi)容,而我們用刷新文件緩沖區(qū)的fflush函數(shù)時再次打開test.txt文本文件時,會發(fā)現(xiàn)里面已經(jīng)有輸入的內(nèi)容。則能夠證實的確有文件緩沖區(qū)的存在。
因為有緩沖區(qū)的存在,C語言在操作文件的時候,需要做刷新緩沖區(qū)或者在文件操作結(jié)束的時候關(guān)閉文件。如果不做,可能導(dǎo)致讀寫文件的問題。
總結(jié)
到此這篇關(guān)于C語言文件操作的文章就介紹到這了,更多相關(guān)C語言文件操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳談全排列next_permutation() 函數(shù)的用法(推薦)
下面小編就為大家?guī)硪黄斦勅帕衝ext_permutation() 函數(shù)的用法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03C語言詳細分析結(jié)構(gòu)體的內(nèi)存對齊規(guī)則
C 數(shù)組允許定義可存儲相同類型數(shù)據(jù)項的變量,結(jié)構(gòu)是 C 編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許你存儲不同類型的數(shù)據(jù)項,本篇讓我們來了解C 的結(jié)構(gòu)體內(nèi)存對齊2022-07-07