文件操作(FILE)
文件
1、文件基本概念
C程序把文件分為ASCII文件和二進制文件,ASCII文件又稱文本文件,二進制文件和文本文件(也稱ASCII碼文件)二進制文件中,數值型數據是以二進制形式存儲的,
而在文本文件中,則是將數值型數據的每一位數字作為一個字符以其ASCII碼的形式存儲,因此,文本文件中的每一位數字都單獨占用一個字節的存儲空間,
而二進制文件則是把整個數字作為一個二進制數存儲的,並非數值的每一位數字都占用單獨的存儲空間,無論一個C語言文件的內容是什麽,
它一律把數據看成是字節構成的序列,即字節流,對文件的存取也是以字節為單位的,輸入/輸出的的數據流僅受程序控制而不受物理符號(如回車換行符)的控制,所以說C語言文件為流式文件
C語言的文件存取有兩種方式:順序存取和直接存取
(C語言有緩沖型和非緩沖型兩種文件系統,緩沖型文件系統是指系統自動自動在內存中為每一個正在使用的文件開辟一個緩沖區,
作為程序與文件之間數據交換的中間媒介,也就是讀文件時,數據先送到緩沖區,再傳給C語言程序或則外存上,緩沖文件系統利用文件指針標識文件,
而非緩沖文件系統是不會自動設置文件緩沖區,緩沖區必須由程序員自己設定,緩沖型中的文件操作,也稱高級文件操作,
高級文件操作函數大多是ANSIC定義的可移植的文件操作函數,具有跨平臺和可移植能力,可解決大多數文件操作問題)
2、文件類型指針
可以用該結構體類型來定義文件類型的指針變量
1 FILE *fp;
FILE是在stdio.h中定義的結構體類型,封裝了與文件有關的信息,如文件句柄、位置指針及緩沖區等,緩沖文件系統為每個被使用的文件在內存中開辟一個緩沖區,
用來存放文件的有關信息,這些信息被保存在一個FILE結構類型的變量中
fp是一個指向FILE結構體類型的指針變量
3、文件操作常用函數
fopen()函數
函數原型為:FILE *fopen(const char *filename, const char *mode);
fopen(文件路徑,文件使用方式);
fopen函數打開filename指定的文件,返回一個指向FILE類型的指針,無論使用哪種方式,當打開文件時出現了錯誤,fopen函數都將返回NULL
常見的文件使用方式:
"r"----以只讀的方式打開文件(該文件必須已經存在,若文件不存在,則會出錯)
"w"----以只寫的方式打開文件,若文件存在則文件長度清為0,即該文件內容會消失。若文件不存在則建立該文件
"a"----以只寫的方式打開文本文件,位置指針移到文件末尾,向文件尾部添加數據,原文件數據保留,若文件不存在則會出錯
"+"----與上面的字符串組合,表示以讀寫的方式打開文本文件,既可向文件中寫入數據,也可從文件中讀出數據
"b"----與上面的字符串組合,表示打開二進制文件
(因為操作系統對於同時打開的文件數目是有限制的,所以在文件使用結束後必須使用fclose關閉文件,否則會出現意想不到的錯誤)
(文件路徑正斜杠反斜杠相關鏈接:https://blog.csdn.net/sszgg2006/article/details/8447176)
1 err = fopen_s(&fp, "E:\\ww.txt", "w");
或者
1 err = fopen_s(&fp, "E:\\ww.txt", "w+");
上述兩條語句打開文件後文件內容自動清除,無論是否存在讀/寫文件操作,都會自動清除
fclose()函數
fclose(文件指針);
若文件關閉成功則返回0,否則返回非0
文件的讀寫
fread()函數
函數原型:unsigned int fread(void *buffer, unsigned int size, unsigned int count, FILE *fp);
函數功能:從fp所指的文件中讀取數據塊並存儲到buffer所指向的內存中,buffer是待讀入數據塊存儲的起始地址,size是每個數據塊的大小(待讀入的每個數據塊的字節數),
count是最多允許讀取的數據塊個數(每個數據塊size個字節),函數返回的是實際讀到的數據塊個數
fwrite()函數
函數原型:unsigned int fwrite(const void *buffer, unsigned int size, unsigned int count, FILE *fp);
函數功能:將buffer指向的內存中的數據塊寫入fp所指的文件,buffer是待輸出數據塊的起始地址,size是每個數據塊的大小(待輸出的每個數據塊的字節數),
count是最多允許寫入的數據塊個數(每個數據塊size個字節),函數返回的是實際寫入的數據塊個數
(tips:用戶指定的內存塊大小,最小為1字節,最大為整個文件)
fread()函數和fwrite()函數是按數據塊的長度來處理輸入/輸出的,在用文本編輯器打開文本文件時可能因發生字符轉換而出現莫名其妙的結果,所以這兩個函數通常用於二進制文件的輸入/輸出
fscanf()函數
函數原型:int fscanf (FILE *fp, const char *format, ……);
fscanf(文件指針, 格式字符, 輸入列表);
第一個參數為文件指針,第二個參數為格式控制符,第三個參數為地址參數表列,後兩個參數的返回值與函數scanf()相同
fprintf()函數
函數原型:int fprintf (FILE *fp, onst char *format, ……);
fprintf(文件指針, 格式字符, 輸出列表);
第一個參數為文件指針,第二個參數為格式控制參數,第三個參數為輸出參數列表,後兩個參數和返回值與函數printf()相同
(用函數fscanf()和fprintf()進行文件的格式化讀寫,讀寫方便容易理解,但輸入時要將ASCII字符轉換成二進制數,輸出時要將二進制數轉換為ASCII字符,耗時較多)
fgets()函數
函數原型:char *fgets(char *s,int n,FILE *fp);
函數功能:該函數從fp所指的文件中讀取字符串並在字符串末尾添加‘\0’,然後存入s,最多讀n-1個字符,當讀到回車換行符、到達文件尾或讀滿n-1個字符時,
函數返回該字符串的首地址,即指針s的值,讀取失敗返回空指針NULL
(與gets()不同的是,fgets()從指定的流讀取字符串,讀到換行符時將換行符也作為字符串的一部分讀到字符串中來)
fputs()函數
函數原型:fputs(_In_z_ const char * _Str, _Inout_ FILE * _File);
str是要輸出的字符串,fp是文件指針,字符串末尾‘\0‘不輸出
函數功能:將字符串輸出到指針fp所指的文件中
(與puts()不同的是,fputs()不會在寫入文件的字符串末尾加上換行符‘\n‘)
fgetc()函數
函數原型:int fgetc (FILE *fp);
函數功能:用於從一個以只讀或讀寫方式打開的文件上讀字符,從fp所值的文件中讀取一個字符,並將位置指針指向下一個字符,若讀取成功,則返回該字符,
若讀取不成功則返回EOF(EOF是一個符號常量,stdio.h中定義為-1)
fputc()函數
函數原型:int fputc(int c, FILE *fp);
fp是由函數fopen()返回的文件指針,c是要輸出的字符(盡管C定義為int型,但只寫入低字節)
函數功能:該函數的功能是將字符c寫到文件指針fp所指的文件上中,若寫入錯誤返回EOF,否則返回字符c
文件的定位
文件的隨機讀寫:文件的隨機訪問允許在文件中隨機定位,並在文件的任何位置直接讀寫數據,為了實現文件的定位,在每一個打開的文件中,都有一個文件位置指針,
也稱為文件位置標記,用來指向當前讀寫文件的位置,它保存了文件中的位置信息,當文件進行順序讀寫時,每讀完一個字節後,該位置指針自動移到下一個字節的位置,
當需要隨機讀寫文件數據時,則需強制移動文件位置指針指向特定的位置
fseek()函數
函數原型:int fseek(FILE *fp,long offset,int fromwhere);
函數功能:將fp的文件位置指針從fromwhere開始移動offset個字節指示下一個要讀取的數據的位置
offset是一個偏移量,它告訴文件位置指針要跳過多少字節,offset為正時,向後移動,為負時,向前移動,ANSIC要求位移量offset是長整型數據(常量數據後要加L),
這樣當文件的長度大於64k時不至於出問題
fromwhere用於確定偏移量計算的起始位置,它的可能取值有3種,SEEK_SET或0,代表文件開始處,SEEK_CUR或1,代表文件當前位置,SEEK_END或2,代表文件結尾處,
通過指定fromwork和offset的值,可使位置指針移動到文件的任意位置,從而實現文件的隨機讀取,如果函數fseek()調用成功,則返回0值,否則返回非0值
兩種特殊使用方式:
將讀寫位置移動到文件開頭:fseek(FILE *stream,0,SEEK_SET);
將讀寫位置移動到文件尾:fseek(FILE *stream,0,0SEEK_END);
rewind()函數
函數原型:void rewind(FILE *fp);
函數功能:將文件位置指針指向文件首字節,即重置位置指針到文件首部
ftell()函數
函數原型:long ftell(FILE *fp);
函數功能:讀取當前文件指針的位置,若函數調用成功,則返回文件的當前讀寫位置,否則返回-1L,函數ftell()用於相對於文件起始位置的字節偏移量來表示返回的當前位置指針
(需要註意的是,當用err = fopen_s(&fp, "E:\\ww.txt", "a+");打開文件後文件指針移到文件末尾,此時文件起始位置還是為文件頭部並不是文件末尾)
文件檢測
ferror()函數
函數原型:int ferror(FILE *stream);
函數功能:它的一般調用形式為 ferror(fp);如果ferror返回值為0(假),表示未出錯。如果返回一個非零值,表示出錯。應該註意,對同一個文件 每一次調用輸入輸出函數,
均產生一個新的ferror函數值,因此,應當在調用一個輸入輸出函數後立即檢 查ferror函數的值,否則信息會丟失。在執行fopen函數時,ferror函數的初始值自動置為0
feof()函數
函數原型:int feof(FILE *fp);
函數功能:檢測流上的文件結束符,如果文件結束,則返回非0值,否則返回0,文件結束符只能被clearerr()函數清除
(函數feof()總是在讀完文件所有內容後再執行一次讀文件操作(將文件結束符讀走,但不顯示)才能返回真(非0)值)
C語言為了提高數據的輸入/輸出的速度,在緩沖型文件系統中,給打開的每一個文件建立一個緩沖區,文件內容先被批量地讀入緩沖區,程序進行讀操作時,實際上是從緩沖區中讀取數據,
寫入操作也是如此,首先將數據寫入緩沖區,然後在適當的時候(例如關閉時)再批量寫入磁盤,這樣雖然可以提高I/O的性能,但也有一些副作用,例如在緩沖區內容還未寫入磁盤時,
計算機突然死機或掉電,數據就會丟失,永遠也找不回來,再如緩沖區被寫入無用的數據時,如果不清除,其後的文件讀操作都首先要讀取這些無用的數據
為了解決這個問題C語言提供了fflush()函數
fflush()函數
函數原型:int fflush(FILE *fp);
函數功能:清除緩沖區的內容,對於輸出流來說,fflush函數將已經寫到緩沖區但尚未寫入文件的所有數據寫到文件中,對輸入流來說,其結果是未定義的,
如果在寫的過程中發生錯誤,則返回EOF,否則返回0,(函數功能的另一種描述:無條件的將緩沖區的所有數據寫入物理設備,這樣程序員可自己決定何時清除緩沖區中的數據,以確保輸出緩沖區的內容寫入文件)
ftell()函數
函數原型:long ftell(FILE *fp);
函數功能:讀取當前文件指針位置,若函數調用成功,則返回文件的當前讀寫位置,否則返回-1L
(函數ftell()用於相對於文件起始位置的字節偏移量來表示返回的當前位置指針)
fflush(stdin);刷新標準輸入緩沖區,把輸入緩沖區裏的東西丟棄[非標準]
fflush(stdout);刷新標準輸出緩沖區,把輸出緩沖區裏的東西打印到標準輸出設備上
fflush(NULL);將清洗所有的輸出流
由於ANSIC規定函數fflush()處理輸出數據流、確保輸出緩沖區中的內容文件,但並未對清理輸入緩沖區作出任何規定,只是部分編譯器增加了此項功能,因此使用fflush(stdin)來清除緩沖區的內容,可能會帶來移植性的問題
(fflush on input stream is an extension to the C standard(fflush 操作輸入流是對 C 標準的擴充))
使用 fflush(stdin)是不正確的,至少是移植性不好的,因為那樣的代碼在一些環境下可能正常工作,但在另一些環境下則會出錯,這導致程序不可移植,所以只能在寫入文件的時候使用fflush
4、標準輸入/輸出重定向
實際上,對於終端設備,系統會自動打開3個標準文件:標準輸入、標準輸出和標準錯誤輸出,相應的,系統定義了3個特別的文件指針常數:stdin、stdout、stderr,
分別指向標準輸入、標準輸出和標準錯誤文件,這3個文件都以標準終端設備作為輸入/輸出對象,在默認情況下,標準輸入設備時鍵盤,標註輸出設備是屏幕
fprintf()是printf()的文件操作版,二者的差別在於fprintf()多了一個FILE *類型的參數fp,如果為其提供的第1個參數時stdout,那麽它就和printf()完全一樣,
同理可推廣到fputc()和putchar()等其他函數,
例如:
putchar(c);和fputc(c,stdout);等價
getchar();和fgetc(stdin);等價
puts(str)和fputs(str,stdout);等價
但函數fgets()與gets()不同,從如下函數原型可知其區別在於fgets()還多了一個參數size
char *fgets(char *s,int size,FILE *stream);
char *gets(char *s);
fgets()用其第二個參數size來說明輸入緩沖區的大小,使讀入的字符數不能超過限定緩沖區的大小,從而達到防止緩沖區溢出攻擊的目的,
假如已定義一個有32字節的緩沖區buffer[32],那麽在下面兩條讀字符串的語句中,後者的安全性更高
gets(buffer);
fgets(buffer,sizeof(buffer),stdin);//安全性更高
雖然系統隱含的I/O是指終端設備,但其實標準輸入和標準輸出是可以重新定向的,操作系統可以重新定向它們到其他文件或具有文件屬性的設備,只有標準錯誤輸出不能進行一般的輸出重定向,
例如,在沒有顯示器的主機上,把標準輸出定向到打印機,各種程序不用做任何改變,輸出內容就自動從打印機輸出
這裏用“<”表示輸入重定向,用“>”表示輸出重定向,例如:假設exefile時可執行程序文件名,執行該程序時,需要輸入數據,現在如果要從文件file.in中讀取數據,而非鍵盤輸入,
那麽在DOS命令提示符下,只要鍵入如下命令行即可
C:\exefile<file.in
於是exefile的標準輸入就被“<”重定向到了file.in,c此時程序exefile只會專心致誌地從文件file.in中讀取數據,而不再理會你此後按下的任何一個按鍵,
再如,若鍵入如下命令行
C:\exefile>file.out
於是,exefile的標準輸出就被“>”重定向到了文件file.out中,此時程序exefile的所有輸出內容都被輸出到了文件file.out中,而屏幕上沒有任何顯示
例:
1 #include <stdio.h>
2
3 main()
4 {
5 int c;
6
7 scanf_s("%d", &c);
8 printf("%d", c);
9
10 return 0;
11 }
將exe文件移到E盤,新建一個te.txt輸入10然後保存,打開DOS命令行,轉到E盤,輸入test1.exe<te.txt回車,則te.txt文件中的10作為輸入值,輸出值為10
5、文件操作補充
因為在Microsoft Visual C++ 2010 Express中使用fopen,fscanf等函數編譯器會顯示警告,
This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
所有在文件操作時將改用fopen_s、fscanf_s等函數
fopen_s()函數
函數原型:errno_t fopen_s( FILE** pFile, const char *filename, const char *mode );
pFile----文件指針將接收到打開文件指針指向的指針
infilename----文件名
mode----允許訪問的類型
fopen_s()打開文件成功返回0值,否則返回非0值
須定義另外一個變量errno_t err
例:
1 FILE *fp;
2 errno_t err;
3 err = fopen(&fp,"E:\\ww.txt","r");
這裏的errno_t是int的別名,在編譯器crtdef.h頭文件中有typedef int errno_t;
fopen()與fopen_s()的區別
fopen_s()函數比fopen()函數多了一個溢出檢測,安全性上有所提升,在使用形式上fopen_s()比fopen()多使用了一個參數,
需要特別註意的是:
fopen的返回值是FILE *,返回的是指向結構體類型的指針
而fopen_s的返回值是errno_t,返回的是errno_t(int)類型的數值
例:fopen()函數
1 fp = fopen("E:\\ww.txt", "r");
fopen_s()函數
1 errno_t err;
2 err = fopen_s(&fp,"E:\\ww.txt","r");
fscanf_s()函數
函數原型:fscanf_s(_Inout_ FILE * _File, _In_z_ _Scanf_s_format_string_ const char * _Format, ...);
fscanf_s和fscanf的區別
在使用形式上fopen_s()比fopen()多使用了一個參數,第四個參數是字節數(註意長度(strlen)和字節數(sizeof)的區別)
例:fscanf()函數
1 fscanf(fp, "%c", &c);
fscanf_s()函數
1 fscanf_s(fp, "%c", &c,sizeof(char));
6、上述文件操作函數代碼示例
(前提條件:在E盤根目錄下新建一個txt文檔命名為ww.txt,內容輸入about保存)
fopen()函數----fopen(文件路徑, 文件使用方式);
1 FILE *fp;
2 fp = fopen("E:\\ww.txt", "r");
fopen_s()函數----fopen_s(指向該文件指針的指針, 文件路徑, 文件使用方式);
1 errno_t err;
2 err = fopen_s(&fp, "E:\\ww.txt", "r");
fread()函數----fread(內存首地址, 數據塊大小, 數據塊個數, 文件指針);
1 char ss[20];
2 fread(ss, sizeof(char), 4, fp);
fwrite()函數----fwrite(內存首地址, 數據塊大小, 數據塊個數, 文件指針);
1 char ss[20] = "aabb";
2 fwrite(ss, sizeof(char), 4, fp);
fscanf_s()函數----fscanf_s(文件指針, 格式參數, 存入地址, 字節數)
1 fscanf_s(fp, "%c", &c,sizeof(char));
fprintf()函數----fprintf(文件指針, 格式參數, 輸出列表)
1 char c = ‘a‘;
2 fprintf(fp, "%c", c);
fseek()函數----fseek(文件指針, 指針偏移量, 起始位置);
1 fseek(fp, sizeof(char), SEEK_SET);
fgets()函數----fgets(內存首地址, 字符數, 文件指針);
1 char ss[20];
2 fgets(ss, 20, fp);
fgetc()函數----fgetc(文件指針);
1 char ss[20];
2 ss[0] = fgetc(fp);
3 printf("%c", ss[0]);
fputc()函數----fputc(變量名, 文件指針)
1 int c = ‘a‘;
2 fputc(c, fp);
也可寫成
fputc(‘c‘, fp);
fputs()函數----fputs(字符串, 文件指針)
1 char ss[20] = "aaaa";
2 fputs(ss, fp);
也可寫成
1 fputs("aaaa", fp);
文件操作(FILE)