C語言中的文件操作實例分析
這篇文章主要介紹了C語言中的文件操作實例分析的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇C語言中的文件操作實例分析文章都會有所收獲,下面我們一起來看看吧。
1.為什么使用文件
在學習結構體時,寫了一個簡易的通訊錄的程序,當程序運行起來的時候,可以在通訊錄中增加和刪除數據,此時數據是存放在內存當中的,當程序退出的時候,通訊錄中的數據自然就不存在了,等下次通訊錄運行的時候,數據又得重新錄入了,這樣的通訊錄使用起來會有點難受。
所以應該通訊錄應該要能夠把數據給記錄下來,只有選擇刪除的時候,數據才不復存在。而這就涉及到了數據持久化的問題,一般數據持久化的方法有,把數據存放在磁盤文件、存放到數據庫等方式。
使用文件可以將數據直接存放到電腦的硬盤上,做到了數據的持久化。
2.什么是文件
磁盤上的文件是文件。
但是在程序設計中,文件可以分為兩種:程序文件和數據文件(從文件功能的角度來分類)
2.1程序文件
包括源程序文件(后綴為.c),目標文件(windows環境后綴為.obj),可執行程序(windows環境后綴為.exe)
平時用來寫C語言代碼的那個文件就是源程序文件
可執行程序就是代碼運行起來后彈出的那個黑框框
目標文件就是可執行程序在形成過程中生成的文件
2.2數據文件
文件的內容不一定是程序,而是程序運行起來時讀寫的數據,比如程序運行需要從中讀取數據的文件,或者輸出內容的文件。
以下的內容基本都是圍繞這個數據文件來展開的,而在之前所處理數據的輸入以及輸出都是以終端(終端就是輸入輸出設備)為對象的,即從終端的鍵盤上輸入數據,運行結果輸出(顯示)到顯示器上。
有時候會把信息輸出到磁盤上面,當需要的時候再從磁盤上讀取數據到內存中使用,這里處理的就是磁盤上文件。
2.3文件名
一個文件要有唯一的文件標識,以便用戶識別和引用。
為了方便起見,文件標識通常被稱為文件名。
文件名包含三個部分:文件路徑+文件名主干+文件后綴
例如:C:\code\test.txt
3.文件的打開和關閉
3.1文件指針
緩沖系統中,關鍵的概念是“文件類型指針”,簡稱“文件指針”
每個被使用的文件都在內存中開辟了一個文件信息區,用來存放文件的相關信息(如文件的名字,文件的狀態以及文件的位置等)。這些信息是保存在一個結構體變量中的,而這個結構體類型是由系統來聲明的,取名FILE。
例如,在VS2013編譯環境下提供的stdio.h頭文件中有以下的文件類型聲明:
struct?_iobuf?{ ????char?*_ptr; ????int??_cnt; ????char?*_base; ????int??_flag; ????int??_file; ????int??_charbuf; ????int??_bufsiz; ????char?*_tmpfname; ???}; typedef?struct?_iobuf?FILE;
不同的C編譯器的FILE類型包含的內容不完全相同,但是大同小異。
每當打開一個文件的時候,系統會根據文件的情況自動創建一個FILE結構的變量,并填充其中的信息,使用者不必關心其中的細節。
一般通過一個FILE的指針來維護這個FILE結構的變量,這樣使用起來更加方便。
創建一個FILE*的指針變量:
FILE* pf://文件指針變量
?定義pf是一個指向FILE類型數據的文件指針變量,可以使pf指向某個文件的文件信息區(一個結構體變量),通過該文件信息區中的信息就能夠訪問該文件,也就是說,通過文件指針變量能夠找到與他關聯的文件。
如圖:
每個文件在打開的時候都會在內存中開辟一個文件信息區,這個文件信息區就是FILE結構體類型的變量,而此時文件指針就會指向這個變量。
3.2文件的打開和關閉
文件在讀寫之前應該先打開文件,在使用結束之后應該關閉文件。
在編寫程序的時候,在打開文件的同時,都會放回一個FILE*的指針變量指向該文件,也相當于建立了指針和文件的關系。
ANSI C規定使用fopen函數來打開文件,使用fclose函數來關閉文件。
打開文件:
FILE* fopen(const char* filename, const char* mode);
關閉文件:
int fclose (FILE* stream);?
用只寫的方式打開文件:
代碼如下:
#include?<stdio.h> int?main() { //打開文件 FILE*?pf?=?fopen("test.txt",?"w"); if?(pf?==?NULL)//遇到錯誤時函數會返回NULL指針 { perror("fopen"); return?1; } //文件操作 //? //關閉文件 fclose(pf); pf?=?NULL;//把文件指針置空,防止野指針的問題 return?0; }
在相應的目錄下創建了一個test.txt文件,如果文件不存在,則進行創建,如果文件存在,則對文件的內容進行銷毀
用只讀的方式打開文件:
代碼如下:
int?main() { //打開文件 FILE*?pf?=?fopen("test.txt",?"r"); //文件操作 //? //關閉文件 fclose(pf); pf?=?NULL;//把文件指針置空,防止野指針的問題 return?0; }
相應的目錄下必須要存在這個test.txt文件,否則編譯器會報錯。
注意:在相應的目錄下查看文件時要在查看中打開文件擴展名
以上fopen函數中的文件名參數寫的都是相對路徑,如果要寫絕對路徑的話要從對應的根目錄開始寫。
4.文件的順序讀寫
先來認識一些輸入和輸出函數:
這里的輸出流和輸出流是一個抽象的概念,比如說一些外部設備如鍵盤、顯示器、U盤等等,這些設備的輸入和輸出方式都是不同的,這個時候C語言中的庫將這些輸入和輸出的方式都封裝成一個流,只需要知道這個流就能完成一個輸入和輸出,而不用去學習硬件上的其他知識。
再打開編譯器的時候,會默認打開三個流,分別是標準輸入流、標準輸出流和標準錯誤流
標準輸入流:stdin,指的是鍵盤
標準輸出流:stdout,指的是屏幕
標準錯誤流:stderr,也是屏幕
接下來具體講講輸入和輸出函數
字符輸出函數:
int? fputc(int c, FILE* stream);
fputc會返回被輸出字符的ASCII碼值,遇到錯誤時返回EOF。?
#include?<stdio.h> int?main() { //打開文件 FILE*?pf?=?fopen("test.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } //寫文件 char?ch?=?0; for?(ch?=?'a';?ch?<=?'z';?ch++) { fputc(ch,?pf); } //關閉文件 fclose(pf); pf?=?NULL; return?0; }
將字符a到z寫到文件當中,也可以理解為將數據輸出到文件里面,因為適用于所有輸出流,所以這里的參數可以用文件流,文件流也是文件指針。
此時可以查看test.txt里面的數據:
字符輸入函數:
int fgetc(FILE* stream);
fgetc會返回讀到的字符的ASCII碼值,當讀到文件末尾或者遇到錯誤時會返回EOF。
int?main() { //打開文件 FILE*?pf?=?fopen("test.txt",?"r"); if?(pf?==?EOF) { perror("fopen"); return?1; } //讀文件?-?輸入操作 int?ch?=?0; while?((ch?=?fgetc(pf))?!=?EOF) { printf("%c?",?ch); } //關閉文件 fclose(pf); pf?=?NULL; return?0; }
從剛剛寫的test.txt中讀取信息,也就是從test.txt上的數據輸入到了ch中,并在屏幕上輸出。
可以將鍵盤和顯示器作為輸入和輸出的參數:
int?main() { int?ch?=?fgetc(stdin); fputc(ch,?stdout); return?0; }
文本行輸出函數:
int fputs(const char* string, FILE* stream);
成功則返回非負指,遇到錯誤則返回EOF
int?main() { //打開文件 FILE*?pf?=?fopen("test1.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } //寫文件 fputs("abcdefg\n",?pf); fputs("hijklmn\n",?pf); //關閉文件 fclose(pf); pf?=?NULL; return?0; }
將字符串輸出到文件中
?此時打開test1.txt查看數據:
如果輸出流寫stdout,可以輸出到屏幕上來
文本行輸入函數:
?char* fgets(char* string, int n, FILE* stream);
將輸入的數據存儲到string,會最多讀取n-1個字符,最后1個字符為'\0',返回值為讀取到的字符串,遇到錯誤時或EOF時返回NULL指針。
int?main() { //打開文件 FILE*?pf?=?fopen("test1.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } //寫文件 char?arr[256]?=?{?0?}; while?(fgets(arr,?256,?pf)?!=?NULL) { fputs(arr,?stdout); } //關閉文件 fclose(pf); pf?=?NULL; return?0; }
將剛才test1.txt里面的內容作為輸入的數據,然后在屏幕上將數據輸出。
格式化輸出函數:
int fprintf( FILE *stream, const char *format [, argument ]...);?
和printf很像,只是printf的默認輸出流是標準輸出流,而這里的參數stream適用于所有的輸出流
struct?S { char?name[20]; int?age; double?score; }; ? int??main() { struct?S?s?=?{?"zhangsan",?20,?75.5?}; //打開文件 FILE*?pf?=?fopen("test2.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } //寫文件 fprintf(pf,?"%s?%d?%f",?s.name,?s.age,?s.score); //關閉文件 fclose(pf); pf?=?NULL; return?0; }
此時已經把數據寫入到了test2.txt里面,打開文件來查看一下:
?同樣的,stream位置的參數可以是標準輸出流,這樣數據就會輸出到屏幕上面來了
struct?S { char?name[20]; int?age; double?score; }; ? int??main() { struct?S?s?=?{?"zhangsan",?20,?75.5?}; fprintf(stdout,?"%s?%d?%f",?s.name,?s.age,?s.score); return?0; }
格式化輸入函數:
int fscanf( FILE *stream, const char *format [, argument ]... );?
fscanf和scanf和很像,只是scanf默認stream位置的參數是標準輸入流,也就是鍵盤。
int??main() { struct?S?s?=?{0}; //打開文件 FILE*?pf?=?fopen("test2.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } //讀文件 fscanf(pf,?"%s?%d%?lf",?s.name,?&(s.age),?&(s.score)); fprintf(stdout,?"%s?%d?%lf",?s.name,?s.age,?s.score); //關閉文件 fclose(pf); pf?=?NULL; return?0; }
?將數據從文件上讀取,然后在屏幕上輸出。
?再來認識兩個與printf和scanf很像的函數:
sprintf:將格式化的數據轉換為字符串
int sprintf( char *buffer, const char *format [, argument] ... );
?sscanf:將字符串轉換為格式化的數據
int sscanf( const char *buffer, const char *format [, argument ] ... );
sprintf的使用:
struct?S { char?name[20]; int?age; double?score; }; ? int?main() { char?buf[256]?=?{?0?}; struct?S?s?=?{?"wangwu",?30,?85.5?}; sprintf(buf,?"%s?%d?%lf",?s.name,?s.age,?s.score); printf("%s\n",?buf); return?0; }
將結構體的數據轉換為字符串存入buf中,然后將buf在屏幕上輸出出來。?
?sscanf的使用:
struct?S { char?name[20]; int?age; double?score; }; ? int?main() { char?buf[256]?=?{?0?}; struct?S?s?=?{?"wangwu",?30,?85.5?}; sprintf(buf,?"%s?%d?%lf",?s.name,?s.age,?s.score); //將字符串轉化為格式化的數據 struct?S?tmp?=?{?0?}; sscanf(buf,?"%s?%d?%lf",?tmp.name,?&(tmp.age),?&(tmp.score)); printf("%s?%d?%lf",?tmp.name,?tmp.age,?tmp.score); return?0; }
也可以認為是從字符串中提取數據存入到結構體tmp中來。
這里列在一起方便對比一下:
?二進制輸出函數:
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
buffer:指向被讀取的數據的指針
size:被讀取的項目的字節大小
count:被讀取的項目數的最大值
stream:文件流
fwrite的使用:
struct?S { char?name[20]; int?age; double?score; }; ? int?main() { struct?S?s?=?{?"lisi",?15,?95.5?}; //打開文件 FILE*?pf?=?fopen("test3.txt",?"wb"); if?(pf?==?NULL) { perror("fopen"); return?1; } //寫文件 fwrite(&s,?sizeof(struct?S),?1,?pf); //關閉文件 fclose(pf); pf?=?NULL; return?0; }
?因為是以二進制的方式寫的,所以文件中存放的數據是這樣子的:
二進制輸入函數:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );?
跟fwrite相反,將文件中的數據作為輸入數據存儲到buffer指向的空間中。
fread的使用:
struct?S { char?name[20]; int?age; double?score; }; ? int?main() { struct?S?s?=?{?0?}; //打開文件 FILE*?pf?=?fopen("test3.txt",?"rb"); if?(pf?==?NULL) { perror("fopen"); return?1; } //讀文件 fread(&s,?sizeof(struct?S),?1,?pf); printf("%s?%d?%lf\n",?s.name,?s.age,?s.score); //關閉文件 fclose(pf); pf?=?NULL; return?0; }
5.文件的隨機讀寫
5.1fseek
根據文件指針的位置和偏移量來定位文件指針
int fseek( FILE *stream, long offset, int origin );
stream:文件流
offset:相對于此時文件指針的位置的偏移量,單位是字節
origin:文件指針此時的位置
origin可以分為3個值:
SEEK_CUR - 文件指針當前的位置
SEEK_SET - 文件開始的位置
SEEK_END - 文件末尾的位置
先在對應的目錄下創建一個test.txt文件,往里面寫入a到f的字符
#include?<stdio.h> int?main() { FILE*?pf?=?fopen("test.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } //把a到f的字符寫入文件 char?ch?=?0; for?(ch?=?'a';?ch?<=?'f';?ch++) { fputc(ch,?pf); } fclose(pf); pf?=?NULL; return?0; }
fseek的使用:
int?main() { FILE*?pf?=?fopen("test.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } //讓文件指針從此時的位置向后移動5個字節 fseek(pf,?5,?SEEK_CUR); int?ch?=?fgetc(pf); printf("%c\n",?ch); fclose(pf); pf?=?NULL; return?0; }
找到f的位置并將其讀取,輸出在屏幕上,結果是f
offset輸入負值也可以讓指針往前走
int?main() { FILE*?pf?=?fopen("test.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } //讓文件指針從文件尾的位置向前移動3個字節 fseek(pf,?-3,?SEEK_END); int?ch?=?fgetc(pf); printf("%c\n",?ch); fclose(pf); pf?=?NULL; return?0; }
最終屏幕上輸出的結果是d
注意:文件末尾是f之后的那個位置
5.2ftell
放回文件指針相對于起始位置的偏移量
long int ftell ( FILE * stream );
ftell的使用:?
int?main() { FILE*?pf?=?fopen("test.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } fseek(pf,?0,?SEEK_END); long?size??=?ftell(pf); printf("%ld\n",?size); fclose(pf); pf?=?NULL; return?0; }
對于test.txt這個文件,文件指針末尾的位置相對于起始位置是6.
使用比較簡單,了解一下即可
5.3rewind
讓文件指針回到文件的起始位置
void rewind( FILE *stream );?
rewind的使用:
int?main() { FILE*?pf?=?fopen("test.txt",?"r"); if?(pf?==?NULL) { perror("fopen"); return?1; } fseek(pf,?0,?SEEK_END); //從文件尾回到文件起始位置 rewind(pf); long?size??=?ftell(pf); printf("%ld\n",?size); fclose(pf); pf?=?NULL; return?0; }
文件指針先是走到文件末尾,最后回到起始位置,所以最終屏幕輸出結果是0
6.文本文件和二進制文件
?根據數據的儲存形式,數據文件被稱為文本文件和二進制文件。
數據在內存中以二進制的形式存儲,如果不加轉換的輸出到外存,就是二進制文件。外存可以理解為硬盤。
如果要求在外存上以ASCII碼的形式存儲,則需要在存儲前轉換。以ASCII字符的形式存儲的文件就是文本文件。前面fputc就是以ASCII字符的形式將數據存儲在文件中。
下面來看一個數據在內存中是怎么存儲的
字符一律以ASCII形式進行存儲,數值型數據即可以用ASCII形式存儲,也可以使用二進制形式來存儲。
假設有一個整數為10000,如果以ASCII碼的形式輸出到磁盤,則在磁盤中占用5個字節(每個字符占用一個字節),而如果以二進制的形式輸出到磁盤,則在磁盤上占用4個字節。
直接在記事本中輸入10000,就是以ASCII碼的形式進行存儲。
可以將這個文件在編譯器中打開
然后選擇二進制的打開方式:
然后可以看到顯示的結果是將二進制的形式轉換為十六進制的形式
剛好對應著10000的ASCII碼的存儲形式
接下來看看以二進制的形式存儲:
int?main() { FILE*?pf?=?fopen("test.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } int?a?=?10000; fwrite(&a,?sizeof(int),?1,?pf);//二進制的形式寫 fclose(pf); pf?=?NULL; return?0; }
再以二進制的形式打開test.txt
?由于該編譯器是小端字節序存儲,所以可以看到16進制顯示的數字是反過來的。
7.文件讀取結束的判定
7.1被錯誤使用的feof
文件在讀取的過程中,不能用feof函數的返回值來判斷文件讀取是否結束,而是應當用于在文件讀取結束的時候,判斷是因為讀取失敗造成的文件讀取結束,還是因為遇到文件尾造成的文件讀取結束。
文件的讀取結束判斷:
文本文件的讀取結束判斷:
fgetc函數判斷返回值是否為EOF
fgets函數判斷返回值是否為NULL
二進制文件的讀取結束判斷:
fread判斷返回值(實際讀到的個數)是否小于預計要讀的個數
feof和ferror函數
feof
int feof( FILE *stream );?
?文件是由于讀取到文件尾結束時返回一個非0值,反之返回0
ferror
int ferror( FILE *stream );
文件是由于讀取錯誤而讀取結束時返回一個非0值,反之返回0
文本文件的讀取結束判斷代碼:
int?main() { int?c;?//?注意:int,非char,要求處理EOF FILE*?fp?=?fopen("test.txt",?"r"); if?(fp?==?NULL) { perror("fopen"); return?1; } //fgetc?當讀取失敗的時候或者遇到文件結束的時候,都會返回EOF while?((c?=?fgetc(fp))?!=?EOF)?//?標準C?I/O讀取文件循環 { putchar(c); } //判斷是什么原因結束的 if?(ferror(fp)) puts("I/O?error?when?reading"); else?if?(feof(fp)) puts("End?of?file?reached?successfully"); fclose(fp); fp?=?NULL; return?0; }
二進制文件的讀取結束判斷代碼:
enum {? SIZE?=?5? }; ? int?main(void) { double?a[SIZE]?=?{?1.,2.,3.,4.,5.?}; FILE*?fp?=?fopen("test.txt",?"wb"); if?(fp?==?NULL) { perror("fopen"); return?1; } fwrite(a,?sizeof?*?a,?SIZE,?fp);?//?寫?double?的數組 fclose(fp); fp?=?NULL; ? double?b[SIZE]; fp?=?fopen("test.txt",?"rb"); size_t?ret_code?=?fread(b,?sizeof?*?b,?SIZE?+?1,?fp);?//?讀?double?的數組 if?(ret_code?==?SIZE)? { puts("Array?read?successfully,?contents:?"); for?(int?n?=?0;?n?<?SIZE;?++n)? printf("%f?",?b[n]); putchar('\n'); } if?(feof(fp))//判斷是否因為遇到文件尾造成的文件讀取結束 printf("Error?reading?test.txt:?unexpected?end?of?file\n"); else?if?(ferror(fp))?//判斷是否因為讀取失敗造成的文件讀取結束 { perror("Error?reading?test.txt"); } fclose(fp); fp?=?NULL; }
8.文件緩沖區
ANSIC標準采用“緩沖文件系統”處理的數據文件的,所謂緩沖文件系統是指系統自動地在內存中為程序中每一個正在使用的文件開辟一塊“文件緩沖區”。從內存向磁盤輸出數據會先送到內存中的緩沖區,裝滿緩沖區后才一起送到磁盤上。如果從磁盤向計算機讀入數據,則從磁盤文件中讀取數據輸入到內存緩沖區(充滿緩沖區),然后再從緩沖區逐個地將數據送到程序數據區(程序變量等)。緩沖區的大小根據C編譯系統決定的。
圖解:
int?main() { FILE*?pf?=?fopen("test.txt",?"w"); if?(pf?==?NULL) { perror("fopen"); return?1; } fputs("abcdef",?pf);//先將代碼放在輸出緩沖區 printf("睡眠10秒-已經寫數據了,打開test.txt文件,發現文件沒有內容\n"); Sleep(10000); printf("刷新緩沖區\n"); fflush(pf);//刷新緩沖區時,才將輸出緩沖區的數據寫到文件(磁盤) printf("再睡眠10秒-此時,再次打開test.txt文件,文件有內容了\n"); Sleep(10000);//此時睡眠10秒,是為了說明數據是由于fflush的刷新才輸出到文件中的 fclose(pf); //注:fclose在關閉文件的時候,也會刷新緩沖區 pf?=?NULL; return?0; }
fflush函數可以讓數據不充滿緩沖區時就直接輸入或者輸出
關于“C語言中的文件操作實例分析”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“C語言中的文件操作實例分析”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注蝸牛博客行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:niceseo99@gmail.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。
評論