檔案操作—知識點小集結
- 啥是檔案
- 檔案的分類
- 檔案名
- 檔案型別
- 檔案緩沖區
- 檔案指標
- 檔案的打開和關閉
- fopen
- fclose
- 檔案的讀寫
- 檔案的順序讀寫
- fgetc,fputc
- fgetc fputc 與 getchar putchar
- fgets.fputs
- fgets fputs 與 gets puts
- fscanf fprintf 、sscanf sprintf 與scanf printf
- fread,fwrite
- 順序讀寫的含義
- 檔案的隨機讀寫
- fseek
- ftell
- rewind
- 檔案讀取結束判定
- feof 和 ferror
啥是檔案
我們之前說過要實作通訊錄的第三次升級,即檔案版本的通訊錄,那么為啥要有檔案版本的呢?因為我們平時在使用通訊錄這樣的程式的時候,我們不能每次打開都是從頭輸入聯系人吧,因此,這也是為什么我們需要將檔案操作加入通訊錄中,是為了讓我們的程式不只是一次性的,也具有保存資料的功能,
好了,說了一下大概的用途,我們就來了解一下啥是檔案吧
檔案的話,廣義的說,磁盤上的東西都可以成為檔案
檔案的分類
那么在我們的程式設計里面,我們將檔案分成程式檔案和資料檔案
程式檔案:包括源程式檔案(如檔案后綴名帶.c的),目標檔案(Window環境下后綴為.obj),可執行程式(Window環境下后綴為.exe)
資料檔案:不是程式,而是程式運行中可能讀取的資料,比如在程式運行中要從一些檔案里讀取資料,或者向一些檔案里寫入資料
檔案名
一個檔案要有唯一的標識讓用戶識別和使用
檔案名:檔案路徑+檔案名主干+檔案名后綴
比如
對于test.c檔案來說,F:\編程\c語言\2021_3\2021_3_24\test 就是它的檔案路徑,test就是它的檔案名主干,.c就是它的檔案名后綴
這里簡單了解一下就好
檔案型別
這里需要注意,我們的檔案從型別上來說可分為文本檔案和二進制檔案
二進制檔案:資料本來是以二進制的形式存盤在記憶體里面的,如果它不加轉換后直接輸出到外存,就叫二進制檔案
文本檔案:如果在外存中要求以ASCII碼值進行存盤,那么就需要在存盤之前進行轉換,轉換之后以ASCII碼字符形式存盤的檔案就叫文本檔案
那么一個資料在記憶體中是如何存盤的呢?
如果是字符,一律是用ASCII碼方式存盤,數字的話既可以用ASCII碼字符形式存盤,也可以用二進制存盤
假如這里有整數1234,那當它輸入進磁盤存盤的時候,它既可以用四個字符’1’,‘2’,‘3’,‘4’來輸入磁盤,也可以用二進制010011010010來輸入磁盤,
具體用哪種方式就取決于我們用’w’還是’wb’(詳見下文),
檔案緩沖區
啥是檔案緩沖區呢?
ANSIC C標準(美國國家標準協會對C語言的發布的標準)會采用緩沖檔案系統的形式來處理資料檔案,所謂的緩沖檔案系統就是系統會自動的在記憶體中為每一個正在使用的記憶體開辟一塊“檔案緩沖區”,從記憶體向磁盤(螢屏,輸出流都一樣)中輸出的資料會先輸送到輸出緩沖區,等到緩沖區裝滿以后,再送到磁盤中,同理,從磁盤(也可以是從鍵盤,輸入流都一樣)中向記憶體輸入資料時,也會先將資料送到輸入緩沖區內,等到緩沖區裝滿后,再送入到記憶體中,

檔案指標
了解我們上面的檔案緩沖區以后,我們就可以繼續來了解我們的檔案指標,檔案指標和我們之前學習到的指標也差不多,只不過它是指向一個檔案的,但是我們所知道的檔案是并不在程式記憶體中,而是在磁盤中,那么它是如何指向一個檔案呢?
每一個檔案在使用的時候都會在記憶體中開辟一個對應的檔案資訊區,檔案資訊區對于檔案來說相當于函式傳參的時候形參相當于實參的拷貝一樣,如下圖

這些檔案資訊是被存盤在一個開辟的結構體中,如下圖,在stdio.h中我們可以查看以下的檔案型別宣告,

檔案的打開和關閉
明白了檔案時被拷貝在記憶體中的時候,我們就可以更好的理解如何打開和關閉一個檔案,
fopen
fopen 是C語言中打開一個檔案的函式,其原型是:FILE fopen(const char firename,const char mode),第一個引數是打開檔案的名稱,第二個引數是打開檔案的方式,如果打開失敗,那么就會回傳一個空指標,因此在我們打開檔案之后,我們需要對檔案指標進行檢查*,檔案名很好理解,那么打開方式有哪些呢?可以參照下圖并進行記憶,

注意:在測驗中博主發現a+與ab+會建立新檔案,同時依然會報錯
直接讓讀者去記憶確實有點多,下面我給讀者總結一些記憶的技巧和它們各自需要記住的特點
1.首先,r(read 讀),w(write 寫),a(add 追加)這三個一定要記住,
2.對于讀來說,這是當你要從檔案里面輸入(也可以理解成提取)資料的時候使用,你用r打開一個檔案的時候,檔案本身并不會發生改變,
3.對于寫來說,它既有一個剪切的功能,也就是你如果使用寫打開一個檔案,那么源檔案里面的內容將會消失,因此對應的當你將資料寫入檔案的時候相當于在一個空檔案里面寫,寫什么都是新定義的,與之前無關,
4.對于追加來說,它就不會毀壞源檔案里面的內容,因為打開檔案時回傳的檔案指標就指向檔案的末尾,
5.了解了上面幾點后對于b(binary)和+(增加了讀和寫功能),只需要對應的將文本檔案換成二進制檔案,以及功能變成讀寫兼有,
6.另外對于如果檔案不存在的情況,要記住3點,1. r 及其衍生的模式均會報錯 , 2. w 及其衍生的模式永遠會建立一個新的對應檔案,
3. a及其衍生的模式均會報錯
雖然感覺上面的要點有點多,但是你要嘗試理解著去記憶,那么要點會幫助你很好的記憶關于檔案打開的各種模式及其對應的情況,
最后再總結一下,就是a和r不能在無檔案的情況下對檔案進行打開,均會報錯,只有w適用于無檔案的情況,
下面給一些簡單的代碼示例
int main()
{
FILE *pf = fopen("file.txt", "r");
if (pf == NULL)
{
printf("列印失敗\n");
}
fclose(pf);
pf = NULL;
}
fclose
有打開就有關閉,如果你只打開不關閉,就可能會造成下次檔案的打不開,反正記住
打開檔案后一定要記得關閉檔案同時將檔案指標置空
fclose的函式原型:int fclose( FILE stream );
引數的話很簡單,就是你已經打開的檔案指標,回傳值的話,如果你已經成功關閉,那么將會回傳0.否則出錯就會回傳EOF,
那么為什么要將檔案指標置空呢?下面看個實驗


到這里,我們已經可以知道了*,fclose陳述句并不能對指標的值進行置空,只是單純釋放了指標所指向的空間,而此時如果我們對指標解參考,我們仍能訪問到指標所指向的空間,但此時這塊空間已經被置空了,這就會形成一種非法訪問的錯誤,
因此我們還要對指標的值進行置空,**

這時我們對指標進行置空后,我們就無法訪問指標所指向的空間了,就能讓我們的程式更加的安全,
檔案的讀寫
注意一點很容易犯的錯誤:那就是讀就讀,寫就寫,你打開的讀模式,你就使用輸入函式,你打開的寫模式,你就使用輸出函式,不能搞混
檔案的順序讀寫
為啥叫檔案的順序讀寫?何為順序?
不妨你先對這些函式進行了解,了解它們的功能之后我們再談為什么稱這些函式為檔案的順序讀寫,
注意到,在此目錄下有很多函式,那么我們應該如何去了解,或者以何種的方式來了解呢?
結合之前的知識來記憶,在看完下面的了解后,也許你對之前的函式了解就更深了一個層次,
fgetc,fputc
fgetc fputc 與 getchar putchar
fgetc 和 fputc (引數和功能與getc putc 一樣,二者可以相互替換)
我們先來整體了解一下它們的函式原型
fgetc: int fgetc( FILE *stream );//從檔案中讀取一個字符,
getchar: int getchar( void );//從標準輸入(鍵盤)中讀取一個字符
fputc: int fputc( int c, FILE *stream );//輸出一個字符到一個檔案中
putchar: int putchar( int c );//輸出一個字符到標準輸出(控制臺)中
仔細觀看它們兩兩之間的引數差異,我們可以發現帶 f 的,即與檔案有關的,它的后面總是會多一個檔案輸出流和檔案輸入流的檔案指標,其實仔細想一想我們會發現,getchar和putchar也有,只不過它們默認輸出到標準輸出流(控制臺)或者從標準輸入流輸入(鍵盤),只不過是省略了,相當于后者分別是前者的固定板,
那么它們的回傳值呢?
全都相同,如果輸入成功或者輸出成功,那么全部都是回傳你所輸入或者輸出的字符的ASCII碼值,如果發生錯誤,那么就回傳EOF
fgets.fputs
fgets fputs 與 gets puts
fgets: char *fgets( char *string, int n, FILE *stream );
//從stream中讀取最多 n 個字符到string中存盤,同時第n個字符被修改成’\0’來結束一個字串,相當于只讀取n-1個字符,這樣說有點抽象,我們來做個實驗來了解一下吧,

如果更深入了解一點,我們做實驗的時候會發現 ,當我們fgets中第二個引數為n,
如果stream中的字符數num<=n-1,那么就會成功讀取num個字符,,然后在讀取的字串后面加上一個’\0’來結束字串,
如果stream中的字符數num>=n,那么就會成功讀取n個字符,同時將最后一個字符改成’\0’來結束字串,
另外:fgets的回傳值如果讀取出錯或者讀取到檔案末尾,那么將會回傳一個NULL指標,至于如何來判斷是讀取出錯回傳的NULL還是讀取完了回傳的NULL,就要用到我們下面的函式來判斷,請繼續往下看吧
gets: char *gets( char *buffer );//從stdin(標準輸入)中讀取一個字串到buffer中存盤
//注意:gets和scanf都能讀取字串,那么它們有什么區別呢,讀者可以移步到我的另一篇博客,以加深二者之間的了解
又是億個小細節:如何讓scanf像gets一樣能讀取帶空格的字串
fputs: int fputs( const char *string, FILE *stream );將string中的字串輸出到stream中,
//要注意兩點,1.fputs輸出到檔案中時無換行,也就是它會接著你上一次的游標繼續輸出
2.fputs是通過檢索字符’\0’來結束字串,要保證你的字串末尾有字符’\0’,但是’\0’是不會被輸入到檔案中的
puts: int puts( const char *string );將string中的字串輸出到stdout(標準輸出流)中
//要注意的時:puts輸出到stdout中時自帶換行
fscanf fprintf 、sscanf sprintf 與scanf printf
** scanf **: int scanf( const char *format [,argument]… );
相信讀者如果學習到了檔案操作,那么你對scanf已經很了解了,我就不講scanf的用法了
那么對于scanf引數的回傳值呢,記住,如果讀取失敗就回傳EOF我們可以使用這個條件來實作回圈讀取一些資料,直到讀完為止,
要注意一點:如何用scanf讀取帶空格的字串,或者說修改scanf讀取字串的結束條件,請參看:又是億個小細節:如何讓scanf像gets一樣能讀取帶空格的字串
** printf ** : int printf( const char *format [, argument]… );
對于printf的回傳值來說,如果成功就回傳列印的字符數,如果出錯就回傳一個負數
** fscanf ** : int fscanf( FILE *stream, const char *format [, argument ]… );
//格式化的從檔案中讀取資料(輸入)
要注意:fscanf不能在"w"的模式下使用,為什么?因為"w"的模式是’只寫’的,只能寫入不能讀取
** fprintf ** : int fprintf( FILE *stream, const char *format [, argument ]…);
//格式化的向檔案中放入資料(輸出)
** sscanf** : int sscanf( const char *buffer, const char *format [, argument ] … );
//從一個字串中格式化的讀取資料(也就是將一個字串轉換成格式化的資料)
** sprintf**: int sprintf( char *buffer, const char *format [, argument] … );
//將格式化的資料輸出到一個字串中(也就是將格式化的資料轉化成字串)
到這我們對上述學習的函式,我們來做個小總結
1.我們要用聯想記憶的方式去學習,例如通過scanf了解fscanf的操作
2.寫一個記憶的小點,fgetc,fputc,fgets,fputs的檔案流是最后一個引數,而fscanf,fprintf,sscanf,sprintf的檔案流是第一個引數
3.比較特殊的還有一點,fgets中還有第二個引數,那就是你最多讀取的字符數,
fread,fwrite
先來說幾點fread和fwrite的幾點與上述函式之間的不同之處
1.上述函式均適用于所有的輸入流和輸出流,而fread和fwrite只適合檔案流
2.fread和fwrite只能在二進制打開的檔案中使用,即使用"rb",“wb”,"ab"等帶b的模式下,
3.因為是用二進制輸入輸出,我們可以看到fread和fwrite是用位元組來作為單位進行輸入輸出的,
而不能像上面一樣以一個字串,一個字符啥的作為單位來輸入輸出
fread : size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
//這個函式的意思就是你從stream流中讀取count個,每個大小為size的資料并存盤到buffer中,要記住是從后往前
fwrite: size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
//與fread相反,這個函式的意思是從buffer中讀取count個,每個大小為size的資料存盤到檔案stream中,
順序讀寫的含義
順序讀寫就是上述函式在呼叫一次之后檔案指標就會發生偏移
舉個例子
char arr[] = "avaksjd";
FILE *pf = fopen("file.txt","w");
fputc('a', pf);
fputc('a', pf);
fputs("bbbbb", pf);
fputs("\n", pf);
fputs("aaaa",pf);
fclose(pf);
pf = NULL;
//輸出結果是
aabbbbb
aaaa
你會發現當fputc第一次以后,再次使用,第二個a是跟在第二個a后面的,但我們并沒有對pf這個指標進行自增這樣的操作,
這也就是我們為什么稱上述函式叫做檔案的順序讀寫,因為檔案指標是會自動按順序修改的,
那如何解決,或者說使用這種檔案指標的移動呢?
請接著往下看,
檔案的隨機讀寫
先了解一個概念
偏移量:檔案指標的位置相對于檔案起始位置的偏移位元組數
fseek
這是一個根據檔案指標的位置和偏移量來定位檔案指標的函式
函式原型:int fseek( FILE *stream, long offset, int origin );
第一個引數是檔案指標,第二個引數是偏移量,第三個引數是一個參照點,
什么意思呢?
你根據參照點,然后來設定你的偏移量,最終讓檔案指標到達你想讓它到的地方,
參照點共有以下三個:
SEEK_CUR
Current position of file pointer//當前檔案指標所在的地方
SEEK_END
End of file//檔案的末尾
SEEK_SET
Beginning of file//檔案的開頭
例如:
char arr[] = "avaksjd";
FILE *pf = fopen("file.txt","w");
fputc('a', pf);
fputc('a', pf);
fputs("bbbbb", pf);
fputs("\n", pf);
fputs("aaaa",pf);
fseek(pf,0,SEEK_SET);//這樣你就將檔案指標設定為指向檔案開頭了
fclose(pf);
pf = NULL;
這里如何定義,就看你自己的需求
ftell
顧名思義,ftell就是告訴你當前檔案指標相對于檔案起始位置的偏移量,
ftell: long ftell( FILE *stream );//回傳偏移量
舉個例子
char arr[] = "avaksjd";
FILE *pf = fopen("file.txt","w");
fputc('a', pf);
fputc('a', pf);
fputs("bbbbb", pf);
fputs("\n", pf);
fputs("aaaa",pf);
fseek(pf,-ftell(pf),SEEK_CUR);
fputc('c', pf);
fclose(pf);
pf = NULL;
這段代碼的輸出結果是
cabbbbb
aaaa
這樣檔案指標就跳回了檔案開頭,修改了第一個字符
rewind
這個函式最簡單,就是簡單的讓你的檔案指標跳回檔案開頭
rewind: void rewind( FILE *stream );
檔案讀取結束判定
feof 和 ferror
feof:int feof( FILE *stream );
這是一個應用于當檔案讀取結束的時候,到底是發生了錯誤,還是讀取到了檔案末尾,這是一個判斷函式
如果當前的檔案指標是位于檔案末尾,即正確讀取到了檔案末尾,那么feof將回傳一個非零的數字
如果當前的檔案指標不是位于檔案末尾,那么feof將回傳0
(可以記為非零即讀完)
ferror
ferror:int ferror( FILE *stream );
如果檔案中發生了錯誤,那么這個函式將不回傳0,否則,將回傳一個0(這個不能像feof一樣判斷檔案是否位于檔案末尾)
(可以記為零即正確)
注意:這兩個函式不是用來判斷檔案是否結束,而是判斷檔案結束的方式
下面舉個例子
FILE *pf = fopen("file.txt","r");
char tmp;
while (fscanf(pf, "%c", &tmp) != EOF)
{
printf("%c\n",tmp);
}
if (feof(pf))
{
printf("正確讀取\n");//非零即讀完
}
else if (ferror(pf))
{
printf("錯誤讀取\n");//零即正確
}
fclose(pf);
pf = NULL;
到這,我們C語言中的檔案操作就告一段落,希望這篇文章對你有所幫助,如果你想和我一起學習進步就請關注我吧!在C語言專欄我接下來會更新如何利用檔案操作知識來讓我們的通訊錄(之前博客中完成的小程式)變得更加可用,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/271613.html
標籤:其他
上一篇:教你速成指標進階
