10.1 C檔案的有關基本知識
10.1.1 什麼是檔案
檔案有不同的型別,在程式設計中,主要用到兩種檔案:
(1) 程式檔案。包括源程式檔案(字尾為.c)、目標檔案(字尾為.obj)、可執行檔案(字尾為.exe)等。這種檔案的內容是程式程式碼。 |
(2) 資料檔案。檔案的內容不是程式,而是供程式執行時讀寫的資料,如在程式執行過程中輸出到磁碟(或其他外部裝置)的資料,或在程式執行過程中供讀入的資料。如一批學生的成績資料、貨物交易的資料等。 |
說明: 為了簡化使用者對輸入輸出裝置的操作,使使用者不必去區分各種輸入輸出裝置之間的區別,作業系統把各種裝置都統一作為檔案來處理。從作業系統的角度看,每一個與主機相連的輸入輸出裝置都看作一個檔案。例如,終端鍵盤是輸入檔案,螢幕和印表機是輸出檔案。 |
檔案(file)一般指儲存在外部介質上資料的集合。作業系統是以檔案為單位對資料進行管理的。
輸入輸出是資料傳送的過程,資料如流水一樣從一處流向另一處,因此常將輸入輸出形象地稱為流(stream),即資料流。
流表示了資訊從源到目的端的流動。在輸入操作時,資料從檔案流向計算機記憶體,在輸出操作時,資料從計算機流向檔案(如印表機、磁碟檔案)。
C的資料檔案由一連串的字元(或位元組)組成,而不考慮行的界限,兩行資料間不會自動加分隔符,對檔案的存取是以字元(位元組)為單位的。輸入輸出資料流的開始和結束僅受程式控制而不受物理符號(如回車換行符)控制,這就增加了處理的靈活性。這種檔案稱為流式檔案。
10.1.2 檔名
一個檔案要有一個唯一的檔案標識,以便使用者識別和引用。
檔案標識包括3部分: (1)檔案路徑; (2)檔名主幹; (3)檔案字尾。
(1) 檔案路徑表示檔案在外部儲存裝置中的位置 |
(2) 檔名主幹的命名規則遵循識別符號的命名規則 |
(3) 檔案字尾用來表示檔案的性質 |
常見的字尾有:mp3 mp4 docx pptx jpg png 等
為方便起見,檔案標識常被稱為檔名,但應瞭解此時所稱的檔名,實際上包括以上3部分內容,而不僅是檔名主幹。
根據資料的組織形式,資料檔案可分為ASCII檔案和二進位制檔案。
說明: 資料在記憶體中是以二進位制形式儲存的,如果不加轉換地輸出到外存,就是二進位制檔案,可以認為它就是儲存在記憶體的資料的映像,所以也稱之為映像檔案(image file)。如果要求在外存上以ASCII程式碼形式儲存,則需要在儲存前進行轉換。ASCII檔案又稱文字檔案(text file),每一個位元組存放一個字元的ASCII程式碼。 |
既可以用ASCII形式儲存,也可以用二進位制形式儲存。
說明: 用ASCII碼形式輸出時位元組與字元一 一對應,一個位元組代表一個字元,因而便於對字元進行逐個處理,也便於輸出字元。但一般佔儲存空間較多,而且要花費轉換時間(二進位制形式與ASCII碼間的轉換)。用二進位制形式輸出數值,可以節省外存空間和轉換時間,把記憶體中的儲存單元中的內容原封不動地輸出到磁碟(或其他外部介質)上,此時每一個位元組並不一定代表一個字元。 |
簡單來說,就是如果使用ASCII碼需要進行轉換,而使用二進位制則不需要轉換。
10.1.4 檔案緩衝區
ANSI C標準採用“緩衝檔案系統”處理資料檔案,所謂緩衝檔案系統是指系統自動地在記憶體區為程式中每一個正在使用的檔案開闢一個檔案緩衝區。
解釋: 從記憶體向磁碟輸出資料必須先送到記憶體中的緩衝區,裝滿緩衝區後才一起送到磁碟去。如果從磁碟向計算機讀入資料,則一次從磁碟檔案將一批資料輸入到記憶體緩衝區(充滿緩衝區),然後再從緩衝區逐個地將資料送到程式資料區(給程式變數)。這樣做是為了節省存取時間,提高效率,緩衝區的大小由各個具體的C編譯系統確定。 |
說明: 每一個檔案在記憶體中只有一個緩衝區,在向檔案輸出資料時,它就作為輸出緩衝區,在從檔案輸入資料時,它就作為輸入緩衝區。 |
簡單來說,就是程式執行為了節省存取時間,提高效率,則我們就需要使用到緩衝區。
10.1.5 檔案型別指標
緩衝檔案系統中,關鍵的概念是“檔案型別指標”,簡稱“檔案指標”。
說明: 每個被使用的檔案都在記憶體中開闢一個相應的檔案資訊區,用來存放檔案的有關資訊(如檔案的名字、檔案狀態及檔案當前位置等)。這些資訊是儲存在一個結構體變數中的。該結構體型別是由系統宣告的,取名為FILE。 |
一種C編譯環境提供的stdio.h標頭檔案中有以下的檔案型別宣告:
typedef struct { short level; //緩衝區“滿”或“空”的程度 unsigned flags; //檔案狀態標誌 char fd; //檔案描述符 unsigned char hold; //如緩衝區無內容不讀取字元 short bsize; //緩衝區的大小 unsigned char*buffer; //資料緩衝區的位置 unsigned char*curp; //檔案位置標記指標當前的指向 unsigned istemp; //臨時檔案指示器 short token; //用於有效性檢查 }FILE; |
FILE *fp; //定義一個指向FILE型別資料的指標變數 |
解釋: 可以使fp指向某一個檔案的檔案資訊區(是一個結構體變數),通過該檔案資訊區中的資訊就能夠訪問該檔案。也就是說,通過檔案指標變數能夠找到與它關聯的檔案。如果有n個檔案,應設n個指標變數,分別指向n個FILE型別變數,以實現對n個檔案的訪問。為方便起見,通常將這種指向檔案資訊區的指標變數簡稱為指向檔案的指標變數。 |
注:指向檔案的指標變數並不是指向外部介質上的資料檔案的開頭,而是指向記憶體中的檔案資訊區的開頭。
10.2 開啟與關閉檔案
對檔案讀寫之前應該“開啟(Open)”該檔案,在使用結束之後應“關閉(Close)”該檔案。
說明: “開啟”是指為檔案建立相應的資訊區(用來存放有關檔案的資訊)和檔案緩衝區(用來暫時存放輸入輸出的資料)。在編寫程式時,在開啟檔案的同時,一般都指定一個指標變數指向該檔案,也就是建立起指標變數與檔案之間的聯絡,這樣,就可以通過該指標變數對檔案進行讀寫了。 |
“關閉”是指撤銷檔案資訊區和檔案緩衝區,使檔案指標變數不再指向該檔案,顯然就無法進行對檔案的讀寫了。 |
10.2.1 用fopen函式開啟資料檔案
ANSI C規定了用標準輸入輸出函式fopen來實現開啟檔案。
Open函式的呼叫方式為
fopen(檔名,使用檔案方式); |
[例] fopen使用示例
FILE*fp; //定義一個指向檔案的指標變數fp fp=fopen(″a1″,″r″); //將fopen函式的返回值賦給指標變數fp,表示以“讀入”方式開啟名字為a1的檔案 |
在開啟一個檔案時,通知編譯系統以下3個資訊: ① 需要開啟檔案的名字,也就是準備訪問的檔案的名字 ② 使用檔案的方式(“讀”還是“寫”等) ③ 讓哪一個指標變數指向被開啟的檔案 |
使用檔案方式有如下形式:
說明:
(1) 用“r”方式開啟的檔案只能用於向計算機輸入而不能用作向該檔案輸出資料,而且該檔案應該已經存在,並存有資料,這樣程式才能從檔案中讀資料。不能用“r”方式開啟一個並不存在的檔案,否則出錯。 |
(2) 用“w”方式開啟的檔案只能用於向該檔案寫資料(即輸出檔案),而不能用來向計算機輸入。如果原來不存在該檔案,則在開啟檔案前新建立一個以指定的名字命名的檔案。如果原來已存在一個以該檔名命名的檔案,則在開啟檔案前先將該檔案刪去,然後重新建立一個新檔案。 |
(3) 如果希望向檔案末尾新增新的資料(不希望刪除原有資料),則應該用“a”方式開啟。但此時應保證該檔案已存在;否則將得到出錯資訊。在每個資料檔案中自動設定了一個隱式的“檔案讀寫位置標記”,它指向的位置就是當前進行讀寫的位置。如果“檔案讀寫位置標記”在檔案開頭,則下一次的讀寫就是檔案開頭的資料。然後“檔案讀寫位置標記”自動移到下一個讀寫位置,以便讀寫下一個資料。以新增方式開啟檔案時,檔案讀寫位置標記移到檔案末尾。 |
(4) 用“r+”“w+”“a+”方式開啟的檔案既可用來輸入資料,也可用來輸出資料。 |
(5) 如果不能實現“開啟”的任務,fopen函式將會帶回一個空指標值NULL。 |
(6) C標準建議用表10.1列出的檔案使用方式開啟文字檔案或二進位制檔案,但目前使用的有些C編譯系統可能不完全提供所有這些功能,需要注意所用系統的規定。 |
(8) 如果用“wb”的檔案使用方式,並不意味著在檔案輸出時把記憶體中按ASCII形式儲存的資料自動轉換成二進位制形式儲存。輸出的資料形式是由程式中採用什麼讀寫語句決定的。例如,用fscanf和fprintf函式是按ASCII方式進行輸入輸出,而fread和fwrite函式是按二進位制進行輸入輸出。 |
(9) 程式中可以使用3個標準的流檔案——標準輸入流、標準輸出流和標準出錯輸出流。系統已對這3個檔案指定了與終端的對應關係。標準輸入流是從終端的輸入,標準輸出流是向終端的輸出,標準出錯輸出流是當程式出錯時將出錯資訊傳送到終端。程式開始執行時系統自動開啟這3個標準流檔案。 |
注意:我們推薦使用以下方式開啟檔案
//開啟一個檔案的常用方法 if ((fp=fopen(″file1″,″r″))==NULL) { printf(″cannot open this file\n″); exit(0); } |
10.2.2 用fclose函式關閉資料檔案
在使用完一個檔案後應該關閉它,以防止它再被誤用。
解釋: “關閉”就是撤銷檔案資訊區和檔案緩衝區,使檔案指標變數不再指向該檔案,也就是檔案指標變數與檔案“脫鉤”,此後不能再通過該指標對原來與其相聯絡的檔案進行讀寫操作,除非再次開啟,使該指標變數重新指向該檔案。 |
如果不關閉檔案就結束程式執行將會丟失資料。
說明: 在向檔案寫資料時,是先將資料輸出到緩衝區,待緩衝區充滿後才正式輸出給檔案。如果當資料未充滿緩衝區時程式結束執行,就有可能使緩衝區中的資料丟失。用fclose函式關閉檔案時,先把緩衝區中的資料輸出到磁碟檔案,然後才撤銷檔案資訊區。有的編譯系統在程式結朿前會自動先將緩衝區中的資料寫到檔案,從而避免了這個問題,但還是應當養成在程式終止之前關閉所有檔案的習慣。 |
關閉檔案用fclose函式。fclose函式呼叫的一般形式為:
fclose(檔案指標);
如:fclose(fp);
注:fclose函式也帶回一個值,當成功地執行了關閉操作,則返回值為0;否則返回EOF(-1)。
10.3 順序讀寫資料檔案
10.3.1 怎樣向檔案讀寫字元
讀寫一個字元的函式:
函式名 |
呼叫形式 |
功能 |
返回值 |
fgetc |
fgetc(fp) |
從fp指向的檔案讀入一個字元 |
讀成功,帶回所讀的字元,失敗則返回檔案結束標誌EOF(即-1) |
fputc |
fputc(ch,fp) |
把字元ch寫到檔案指標變數fp所指向的檔案中 |
輸出成功,返回值就是輸出的字元;輸出失敗,則返回EOF(即-1) |
說明:
fgetc的第1個字母f代表檔案(file),中間的get表示“獲取”,最後一個字母c表示字元(character),fgetc的含義很清楚: 從檔案讀取一個字元。fputc也類似。 |
[例] 從鍵盤輸入一些字元,並逐個把它們送到磁碟上去,直到使用者輸入一個“#”為止。
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; //定義檔案指標fp char ch,filename[10]; printf("請輸入所用的檔名: "); scanf("%s",filename); //輸入檔名 getchar(); //用來消化最後輸入的回車符 if((fp=fopen(filename,"w"))==NULL) //開啟輸出檔案並使fp指向此檔案 { printf("cannot open file\n"); //如果開啟出錯就輸出“打不開” exit(0); //終止程式 } printf("請輸入一個準備儲存到磁碟的字串(以#結束): "); ch=getchar(); //接收從鍵盤輸入的第一個字元 while(ch!='#') //當輸入′#′時結束迴圈 { fputc(ch,fp); //向磁碟檔案輸出一個字元 putchar(ch); //將輸出的字元顯示在螢幕上 ch=getchar(); //再接收從鍵盤輸入的一個字元 } fclose(fp); //關閉檔案 putchar(10); //向螢幕輸出一個換行符 return 0; } |
說明: 用來儲存資料的檔名可以在fopen函式中直接寫成字串常量形式 ,也可以在程式執行時由使用者臨時指定。
用fopen函式開啟一個“只寫”的檔案(“w”表示只能寫入不能從中讀資料),若成功,函式返回該檔案所建立的資訊區的起始地址給檔案指標變數fp。若失敗,則顯示“無法開啟此檔案”,用exit函式終止程式執行,此函式在stdlib.h標頭檔案中。
用getchar函式接收使用者從鍵盤輸入的字元。注意每次只能接收一個字元。 |
執行結果:
[例] 將一個磁碟檔案中的資訊複製到另一個磁碟檔案中。今要求將上例建立的file1.dat檔案中的內容複製到另一個磁碟檔案file2.dat中。
#include <stdio.h> #include <stdlib.h> int main() { FILE *in,*out; //定義指向FILE型別檔案的指標變數 char ch,infile[10],outfile[10]; //定義兩個字元陣列,分別存放兩個資料檔名 printf("輸入讀入檔案的名字:"); scanf("%s",infile); //輸入一個輸入檔案的名字 printf("輸入輸出檔案的名字:"); scanf("%s",outfile); //輸入一個輸出檔案的名字 if((in=fopen(infile,"r"))==NULL) //開啟輸入檔案 { printf("無法開啟此檔案\n"); exit(0); } if((out=fopen(outfile,"w"))==NULL) //開啟輸出檔案 { printf("無法開啟此檔案\n"); exit(0); } ch=fgetc(in); //從輸入檔案讀入一個字元,賦給變數ch while(!feof(in)) //如果未遇到輸入檔案的結束標誌 { fputc(ch,out); //將ch寫到輸出檔案 putchar(ch); //將ch顯示到螢幕上 ch=fgetc(in); //再從輸入檔案讀入一個字元,賦給變數ch } putchar(10); //顯示完全部字元後換行 fclose(in); //關閉輸入檔案 fclose(out); //關閉輸出檔案 return 0; } |
說明: 在訪問磁碟檔案時,是逐個字元(位元組)進行的,為了知道當前訪問到第幾個位元組,系統用“檔案讀寫位置標記”來表示當前所訪問的位置。開始時“檔案讀寫位置標記”指向第1個位元組,每訪問完一個位元組後,當前讀寫位置就指向下一個位元組,即當前讀寫位置自動後移。
為了知道對檔案的讀寫是否完成,只須看檔案讀寫位置是否移到檔案的末尾。 |
執行結果:
10.3.2 怎樣向檔案讀寫一個字串
讀寫一個字串的函式:
函式名 |
呼叫形式 |
功能 |
返回值 |
fgets |
fgets(str,n,fp) |
從fp指向的檔案讀入一個長度為(n-1)的字串,存放到字元陣列str中 |
讀成功,返回地址str,失敗則返回NULL |
fputs |
fputs(str,fp) |
把str所指向的字串寫到檔案指標變數fp所指向的檔案中 |
輸出成功,返回0;否則返回非0值 |
說明:
fgets中最後一個字母s表示字串(string)。見名知義,fgets的含義是: 從檔案讀取一個字串。 |
fgets函式的函式原型為:
char *fgets(char*str, int n, FILE*fp); |
其作用是從檔案讀入一個字串。呼叫時可以寫成下面的形式:
fgets(str,n,fp); |
解釋: 其中,n是要求得到的字元個數,但實際上只從fp所指向的檔案中讀入n-1個字元,然後在最後加一個′\0′字元,這樣得到的字串共有n個字元,把它們放到字元陣列str中。如果在讀完n-1個字元之前遇到換行符“\n”或檔案結束符EOF,讀入即結束,但將所遇到的換行符“\n”也作為一個字元讀入。若執行fgets函式成功,則返回值為str陣列首元素的地址,如果一開始就遇到檔案尾或讀資料出錯,則返回NULL。 |
fputs函式的函式原型為:
int fputs (char *str, FILE *fp); |
其作用是將str所指向的字串輸出到fp所指向的檔案中。呼叫時可以寫成:
fputs("China",fp); |
解釋: 把字串″China″輸出到fp指向的檔案中。fputs函式中第一個引數可以是字串常量、字元陣列名或字元型指標。字串末尾的′\0′不輸出。若輸出成功,函式值為0;失敗時,函式值為EOF(即-1)。 |
說明:
fgets和fgets這兩個函式的功能類似於gets和puts函式,只是gets和puts以終端為讀寫物件,而fgets和fputs函式以指定的檔案作為讀寫物件。 |
[例] 從鍵盤讀入若干個字串,對它們按字母大小順序排序,然後把排好序的字串送到磁碟檔案中儲存
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { FILE*fp; char str[3][10],temp[10]; //str是用來存放字串的二維陣列,temp是臨時陣列 int i,j,k,n=3; printf("Enter strings:\n"); //提示輸入字串 for(i=0;i<n;i++) gets(str[i]); //輸入字串 for(i=0;i<n-1;i++) //用選擇法對字串排序 { k=i; for(j=i+1;j<n;j++) if(strcmp(str[k],str[j])>0) k=j; if(k!=i) { strcpy(temp,str[i]); strcpy(str[i],str[k]); strcpy(str[k],temp);} } if((fp=fopen("D:\\CC\\string.dat","w"))==NULL) //開啟磁碟檔案 //′\′為轉義字元的標誌,因此在字串中要表示′\′用′\\′ { printf("can′t open file!\n"); exit(0); } printf("\nThe new sequence:\n"); for(i=0;i<n;i++) { fputs(str[i],fp);fputs("\n",fp); //向磁碟檔案寫一個字串,然後輸出一個換行符 printf("%s\n",str[i]); //在螢幕上顯示 } return 0; } |
執行結果:
[例] 從鍵盤讀入若干個字串,對它們按字母大小的順序排序,然後把排好序的字串送到磁碟檔案中儲存。(可以編寫出以下的程式,從檔案string.dat中讀回字串,並在螢幕上顯示)
#include <stdio.h> #include <stdlib.h> int main() { FILE*fp; char str[3][10]; int i=0; if((fp=fopen("D:\\CC\\string.dat","r"))==NULL) //注意檔案路徑必須與前相同 { printf("can′t open file!\n"); exit(0); } while(fgets(str[i],10,fp)!=NULL) { printf("%s",str[i]); i++; } fclose(fp); return 0; } |
執行結果:
10.3.3 用格式化的方式讀寫文字檔案
可以對檔案進行格式化輸入輸出,這時就要用fprintf函式和fscanf函式。
說明: 從函式名可以看到,它們只是在printf和scanf的前面加了一個字母f。它們的作用與printf函式和scanf函式相仿,都是格式化讀寫函式。只有一點不同: fprintf和fscanf函式的讀寫物件不是終端而是檔案 |
它們的一般呼叫方式為:
fprintf(檔案指標, 格式字串, 輸出表列); |
fscanf(檔案指標, 格式字串, 輸出表列); |
使用方式如下:
fprintf (fp,″%d,%6.2f″,i,f); //將int型變數i和float型變數f的值按%d和%6.2f的格式輸出到fp指向的檔案中 |
fscanf (fp,″%d,%f″,&i,&f); //磁碟檔案上如果有字元“3,4.5”,則從中讀取整數3送給整型變數i,讀取實數4.5送給float型變數f |
10.3.4 用二進位制方式向檔案讀寫一組資料
說明:
C語言允許用fread函式從檔案中讀一個資料塊,用fwrite函式向檔案寫一個資料塊。在讀寫時是以二進位制形式進行的。在向磁碟寫資料時,直接將記憶體中一組資料原封不動、不加轉換地複製到磁碟檔案上,在讀入時也是將磁碟檔案中若干位元組的內容一批讀入記憶體。 |
一般呼叫形式為:
fread(buffer, size, count, fp); |
fwrite(buffer, size, count, fp); |
解釋: buffer:是一個地址。對fread,它是用來存放從檔案讀入的資料的儲存區的地址。對fwrite,是要把此地址開始的儲存區中的資料向檔案輸出(以上指的是起始地址)
size:要讀寫的位元組數
count:要讀寫多少個資料項(每個資料項長度為size)
fp:FILE型別指標 |
使用例子如下
float f[10]; fread(f,4,10,fp); //從fp所指向的檔案讀入10個4個位元組的資料,儲存到陣列f中 |
[例] 從鍵盤輸入10個學生的有關資料,然後把它們轉存到磁碟檔案上去。
#include <stdio.h> #define SIZE 10 struct Student_type { char name[10]; int num; int age; char addr[15]; }stud[SIZE]; //定義全域性結構體陣列stud,包含10個學生資料
void save() //定義函式save,向檔案輸出SIZE個學生的資料 { FILE *fp; int i; if((fp=fopen("stu.dat","wb"))==NULL) //開啟輸出檔案stu.dat { printf("cannot open file\n"); return; } for(i=0;i<SIZE;i++) if(fwrite(&stud[i],sizeof(struct Student_type),1,fp)!=1) printf("file write error\n"); fclose(fp); }
int main() { int i; printf("Please enter data of students:\n"); for(i=0;i<SIZE;i++) //輸入SIZE個學生的資料,存放在陣列stud中 scanf("%s%d%d%s",stud[i].name,&stud[i].num, &stud[i].age,stud[i].addr); save(); return 0; } |
執行結果:
[例] 從鍵盤輸入10個學生的有關資料,然後把它們轉存到磁碟檔案上去。
(為了驗證在磁碟檔案stu.dat中是否已存在此資料,可以用以下程式從stu.dat檔案中讀入資料,然後在螢幕上輸出)
#include <stdio.h> #include <stdlib.h> #define SIZE 10 struct Student_type { char name[10]; int num; int age; char addr[15]; }stud[SIZE];
int main() { int i; FILE *fp; if((fp=fopen("stu.dat","rb"))==NULL) //開啟輸入檔案stu.dat { printf("cannot open file\n"); exit(0); } for(i=0;i<SIZE;i++) { fread(&stud[i],sizeof(struct Student_type),1,fp); //從fp指向的檔案讀入一組資料 printf("%-10s %4d %4d %-15s\n",stud[i].name,stud[i].num,stud[i]. age,stud[i].addr); //在螢幕上輸出這組資料 } fclose(fp); //關閉檔案stu_list return 0; } |
執行結果:
[例] 從鍵盤輸入10個學生的有關資料,然後把它們轉存到磁碟檔案上去。
(從磁碟檔案stu_list中讀二進位制資料,並存放在stud陣列中)
##include <stdio.h> #define SIZE 10 struct Student_type { char name[10]; int num; int age; char addr[15]; }stud[SIZE]; //定義全域性結構體陣列stud,包含10個學生資料 void load() { FILE *fp; int i; if((fp=fopen("stu_list","rb"))==NULL) //開啟輸入檔案stu_list { printf("cannot open infile\n"); return; } for(i=0;i<SIZE;i++) if(fread(&stud[i],sizeof(struct Student_type),1,fp)!=1) //從stu_ list檔案中讀資料 { if(feof(fp)) { fclose(fp); return; } printf("file read error\n"); } fclose(fp); } void save() //定義函式save,向檔案輸出SIZE個學生的資料 { FILE *fp; int i; if((fp=fopen("stu.dat","wb"))==NULL) //開啟輸出檔案stu.dat { printf("cannot open file\n"); return; } for(i=0;i<SIZE;i++) if(fwrite(&stud[i],sizeof(struct Student_type),1,fp)!=1) printf("file write error\n"); fclose(fp); } int main() { int i; load(); save(); return 0; } |
總結:
(1) 資料的儲存方式 文字方式: 資料以字元方式(ASCII程式碼)儲存到檔案中。如整數12,送到檔案時佔2個位元組,而不是4個位元組。以文字方式儲存的資料便於閱讀 二進位制方式: 資料按在記憶體的儲存狀態原封不動地複製到檔案。如整數12,送到檔案時和在記憶體中一樣佔4個位元組 |
(2) 檔案的分類 文字檔案(ASCII檔案): 檔案中全部為ASCII字元 二進位制檔案: 按二進位制方式把在記憶體中的資料複製到檔案的,稱為二進位制檔案,即映像檔案 |
(3) 檔案的開啟方式 文字方式: 不帶b的方式,讀寫檔案時對換行符進行轉換 二進位制方式: 帶b的方式,讀寫檔案時對換行符不進行轉換 |
(4) 檔案讀寫函式 文字讀寫函式: 用來向文字檔案讀寫字元資料的函式(如fgetc,fgets,fputc,fputs,fscanf,fprintf等) 二進位制讀寫函式: 用來向二進位制檔案讀寫二進位制資料的函式(如getw,putw,fread,fwrite等) |
10.5 隨機讀寫資料檔案
說明: 對檔案進行順序讀寫比較容易理解,也容易操作,但有時效率不高。而 隨機訪問不是按資料在檔案中的物理位置次序進行讀寫,而是可以對任何位置上的資料進行訪問,顯然這種方法比順序訪問效率高得多。 |
10.5.1 檔案位置標記及其定位
1. 檔案位置標記
為了對讀寫進行控制,系統為每個檔案設定了一個檔案讀寫位置標記(簡稱檔案位置標記或檔案標記),用來指示“接下來要讀寫的下一個字元的位置”。
說明: 一般情況下,在對字元檔案進行順序讀寫時,檔案位置標記指向檔案開頭,這時如果對檔案進行讀/寫的操作,就讀/寫完第1個字元後,檔案位置標記順序向後移一個位置,在下一次執行讀/寫操作時,就將位置標記指向的第2個字元進行讀出或寫入。依此類推,直到遇檔案尾,,此時檔案位置標記在最後一個資料之後。 |
如圖所示:
說明: 對流式檔案既可以進行順序讀寫,也可以進行隨機讀寫。關鍵在於控制檔案的位置標記。如果檔案位置標記是按位元組位置順序移動的,就是順序讀寫。如果能將檔案位置標記按需要移動到任意位置,就可以實現隨機讀寫。所謂隨機讀寫,是指讀寫完上一個字元(位元組)後,並不一定要讀寫其後續的字元(位元組),而可以讀寫檔案中任意位置上所需要的字元(位元組)。即對檔案讀寫資料的順序和資料在檔案中的物理順序一般是不一致的。可以在任何位置寫入資料,在任何位置讀取資料。 |
2. 檔案位置標記的定位
(1) 用rewind函式使檔案位置標記指向檔案開頭 rewind函式的作用是使檔案位置標記重新返回檔案的開頭,此函式沒有返回值。 形式: rewind(檔案指標); |
(2) 用fseek函式改變檔案位置標記 fseek函式一般用於二進位制檔案。 形式: fseek(檔案型別指標, 位移量, 起始點); 說明: “起始點”:用0,1或2代替,0代表“檔案開始位置”,1為“當前位置”,2為“檔案末尾位置” “位移量”:指以“起始點”為基點,向前移動的位元組數(長整型) 使用方式為: fseek (fp,100L,0); //將檔案位置標記向前移到離檔案開頭100個位元組處 fseek (fp,50L,1); //將檔案位置標記向前移到離當前位置50個位元組處 fseek (fp,-10L,2); //將檔案位置標記從檔案末尾處向後退10個位元組 |
(3) 用ftell函式測定檔案位置標記的當前位置 ftell函式的作用是得到流式檔案中檔案位置標記的當前位置,用相對於檔案開頭的位移量來表示。如果呼叫函式時出錯(如不存在fp指向的檔案),ftell函式返回值為-1L。 使用方式: i=ftell(fp); //變數i存放檔案當前位置 if(i==-1L) printf(″error\n″); //如果呼叫函式時出錯,輸出″error″ |
[例] 有一個磁碟檔案,內有一些資訊,要求第1次將它的內容顯示在螢幕上,第2次把它複製到另一檔案上
#include<stdio.h> int main() { char ch; FILE *fp1,*fp2; fp1=fopen("file1.dat","r"); //開啟輸入檔案 fp2=fopen("file2.dat","w"); //開啟輸出檔案 ch=getc(fp1); //從file1.dat檔案讀入第一個字元 while(!feof(fp1)) //當未讀取檔案尾標誌 { putchar(ch); //在螢幕輸出一個字元 ch=getc(fp1); //再從file1.dat檔案讀入一個字元 } putchar(10); //在螢幕執行換行 rewind(fp1); //使檔案位置標記返回檔案開頭 ch=getc(fp1); //從file1.dat檔案讀入第一個字元 while(!feof(fp1)) //當未讀取檔案尾標誌 { fputc(ch,fp2); //向file2.dat檔案輸出一個字元 ch=fgetc(fp1); //再從file1.dat檔案讀入一個字元 } fclose(fp1);fclose(fp2); return 0; } |
執行結果:
10.4.2 隨機讀寫
[例] 在磁碟檔案上存有10個學生的資料。要求將第1,3,5,7,9個學生資料輸入計算機,並在螢幕上顯示出來。
提示:使用rewind和fseek函式
#include<stdio.h> #include <stdlib.h> struct Student_type //學生資料型別 { char name[10]; int num; int age; char addr[15]; }stud[10]; int main() { int i; FILE *fp; if((fp=fopen("stu.dat","rb"))==NULL) //以只讀方式開啟二進位制檔案 { printf("can not open file\n"); exit(0); } for(i=0;i<10;i+=2) { fseek(fp,i*sizeof(struct Student_type),0); //移動檔案位置標記 fread(&stud[i],sizeof(struct Student_type),1,fp); //讀一個資料塊到結構體變數 printf("%-10s %4d %4d %-15s\n",stud[i].name,stud[i].num,stud[i].age,stud[i].addr); //在螢幕輸出 } fclose(fp); return 0; } |
執行結果:
10.5 檔案讀寫的出錯檢測
C提供一些函式用來檢查輸入輸出函式呼叫時可能出現的錯誤。
1. ferror函式
它的一般呼叫形式為: ferror(fp); |
說明: 在呼叫各種輸入輸出函式(如putc,getc,fread,fwrite等)時,如果出現錯誤,除了函式返回值有所反映外,還可以用ferror函式檢查。
如果ferror返回值為0(假),表示未出錯;如果返回一個非零值,表示出錯。 |
2. clearerr函式
說明: clearerr的作用是使檔案出錯標誌和檔案結束標誌置為0。
假設在呼叫一個輸入輸出函式時出現錯誤,ferror函式值為一個非零值。應該立即呼叫clearerr(fp),使ferror(fp)的值變成0,以便再進行下一次的檢測。
只要出現檔案讀寫出錯標誌,它就一直保留,直到對同一檔案呼叫clearerr函式或rewind函式,或任何其他一個輸入輸出函式。 |
注:對同一個檔案每一次呼叫輸入輸出函式,都會產生一個新的ferror函式值,因此,應當在呼叫一個輸入輸出函式後立即檢查ferror函式的值,否則資訊會丟失。在執行fopen函式時,ferror函式的初始值自動置為0。