1. 程式人生 > >C++對二進位制檔案的讀寫操作

C++對二進位制檔案的讀寫操作

二進位制檔案不是以ASCII程式碼存放資料的,它將記憶體中資料儲存形式不加轉換地傳送到磁碟檔案,因此它又稱為記憶體資料的映像檔案。因為檔案中的資訊不是字元資料,而是位元組中的二進位制形式的資訊,因此它又稱為位元組檔案

對二進位制檔案的操作也需要先開啟檔案,用完後要關閉檔案。在開啟時要用ios::binary指定為以二進位制形式傳送和儲存。二進位制檔案除了可以作為輸入檔案或輸出檔案外,還可以是既能輸入又能輸出的檔案。這是和ASCII檔案不同的地方。

用成員函式read和write讀寫二進位制檔案

對二進位制檔案的讀寫主要用istream類的成員函式read和write來實現。這兩個成員函式的原型為

    istream& read(char *buffer,int len);
    ostream& write(const char * buffer,int len);
字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數。呼叫的方式為:
    a. write(p1,50);
    b. read(p2,30);
上面第一行中的a是輸出檔案流物件,write函式將字元指標p1所給出的地址開始的50個位元組的內容不加轉換地寫到磁碟檔案中。在第二行中,b是輸入檔案流物件,read 函式從b所關聯的磁碟檔案中,讀入30個位元組(或遇EOF結束),存放在字元指標p2所指的一段空間內。


[例13.14] 將一批資料以二進位制形式存放在磁碟檔案中。
  1. #include <fstream>
  2. using namespace std;
  3. structstudent
  4. {
  5. char name[20];
  6. int num;
  7. int age;
  8. char sex;
  9. };
  10. int main( )
  11. {
  12. studentstud[3]={"Li",1001,18,'f',"Fun",1002,19,'m',"Wang",1004,17,'f'};
  13. ofstreamoutfile("stud.dat",ios::binary);
  14. if(!outfile)
  15. {
  16. cerr<<
    "open error!"<<endl;
  17. abort( );//退出程式
  18. }
  19. for(int i=0;i<3;i++)
  20. outfile.write((char*)&stud[i],sizeof(stud[i]));
  21. outfile.close( );
  22. return 0;
  23. }
用成員函式write向stud.dat輸出資料,從前面給出的write函式的原型可以看出: 第1個形參是指向char型常變數的指標變數buffer,之所以用const宣告,是因為不允許通過指標改變其指向資料的值。形參要求相應的實參是字元指標或字串的首地址。現在要將結構體陣列的一個元素(包含4個成員)一次輸出到磁碟檔案stud.dat。&tud[i] 是結構體陣列第i個元素的首地址,但這是指向結構體的指標,與形參型別不匹配。因此 要用(char *)把它強制轉換為字元指標。第2個引數是指定一次輸出的位元組數。sizeof (stud[i])的值是結構體陣列的一個元素的位元組數。呼叫一次write函式,就將從&tud[i]開始的結構體陣列的一個元素輸出到磁碟檔案中,執行3次迴圈輸出結構體陣列的3個元素。

其實可以一次輸出結構體陣列的個元素,將for迴圈的兩行改為以下一行:
   outfile.write((char*)&stud[0],sizeof(stud));
執行一次write函式即輸出了結構體陣列的全部資料。

abort函式的作用是退出程式,與exit作用相同。

可以看到,用這種方法一次可以輸出一批資料,效率較高。在輸出的資料之間不必加入空格,在一次輸出之後也不必加回車換行符。在以後從該檔案讀入資料時不是靠空格作為資料的間隔,而是用位元組數來控制。

[例13.15] 將剛才以二進位制形式存放在磁碟檔案中的資料讀入記憶體並在顯示器上顯示。
  1. #include <fstream>
  2. using namespace std;
  3. structstudent
  4. {
  5. stringname;
  6. int num;
  7. int age;
  8. char sex;
  9. };
  10. int main( )
  11. {
  12. studentstud[3];
  13. int i;
  14. ifstreaminfile("stud.dat",ios::binary);
  15. if(!infile)
  16. {
  17. cerr<<"open error!"<<endl;
  18. abort( );
  19. }
  20. for(i=0;i<3;i++)
  21. infile.read((char*)&stud[i],sizeof(stud[i]));
  22. infile.close( );
  23. for(i=0;i<3;i++)
  24. {
  25. cout<<"NO."<<i+1<<endl;
  26. cout<<"name:"<<stud[i].name<<endl;
  27. cout<<"num:"<<stud[i].num<<endl;;
  28. cout<<"age:"<<stud[i].age<<endl;
  29. cout<<"sex:"<<stud[i].sex<<endl<<endl;
  30. }
  31. return 0;
  32. }
執行時在顯示器上顯示:
NO.1
name: Li
num: 1001
age: 18
sex: f

NO.2
name: Fun
num: 1001
age: 19
sex: m

NO.3
name: Wang
num: 1004
age: 17
sex: f

請思考,能否一次讀入檔案中的全部資料,如:
    infile.read((char*)&stud[0],sizeof(stud));
答案是可以的,將指定數目的位元組讀入記憶體,依次存放在以地址&tud[0]開始的儲存空間中。要注意讀入的資料的格式要與存放它的空間的格式匹配。由於磁碟檔案中的資料是從記憶體中結構體陣列元素得來的,因此它仍然保留結構體元素的資料格式。現在再讀入記憶體,存放在同樣的結構體陣列中,這必然是匹配的。如果把它放到一個整型陣列中,就不匹配了,會出錯。

與檔案指標有關的流成員函式

在磁碟檔案中有一個檔案指標,用來指明當前應進行讀寫的位置。在輸入時每讀入 一個宇節,指標就向後移動一個位元組。在輸出時每向檔案輸出一個位元組,指標就向後移動 一個位元組,隨著輸出檔案中位元組不斷增加,指標不斷後移。對於二進位制檔案,允許對指標進行控制,使它按使用者的意圖移動到所需的位置,以便在該位置上進行讀寫。檔案流提供 一些有關檔案指標的成員函式。為了查閱方便,將它們歸納為表13.7,並作必要的說明。

表13.7 檔案流與檔案指標有關的成員函式
成員函式 作 用
gcount() 返回最後一次輸入所讀入的位元組數
tellg() 返回輸入檔案指標的當前位置
seekg(檔案中的位置) 將輸入檔案中指標移到指定的位置
seekg(位移量, 參照位置) 以參照位置為基礎移動若干位元組
tellp() 返回輸出檔案指標當前的位置
seekp(檔案中的位置) 將輸出檔案中指標移到指定的位置
seekp(位移量, 參照位置) 以參照位置為基礎移動若干位元組

幾點說明:
1) 這些函式名的第一個字母或最後一個字母不是g就是p。帶 g的是用於輸入的函式(g是get的第一個字母,以g作為輸入的標識,容易理解和記憶), 帶p的是用於輸出的函式(P是put的第一個字母,以P作為輸出的標識)。例如有兩個 tell 函式,tellg用於輸入檔案,tellp用於輸出檔案。同樣,seekg用於輸入檔案,seekp用於輸出檔案。以上函式見名知意,一看就明白,不必死記。

如果是既可輸入又可輸出的檔案,則任意用seekg或seekp。

2) 函式引數中的“檔案中的位置”和“位移量”已被指定為long型整數,以位元組為單位。“參照位置”可以是下面三者之一:
    ios::beg  檔案開頭(beg是begin的縮寫),這是預設值。
    ios::cur  指標當前的位置(cur是current的縮寫)。
    ios::end  檔案末尾。
它們是在ios類中定義的列舉常量。舉例如下:
    infile.seekg(100);  //輸入檔案中的指標向前移到位元組位置
    infile.seekg(-50,ios::cur);  //輸入檔案中的指標從當前位置後移位元組
    outfile.seekp(-75,ios::end);  //輸出檔案中的指標從檔案尾後移位元組

隨機訪問二進位制資料檔案

一般情況下讀寫是順序進行的,即逐個位元組進行讀寫。但是對於二進位制資料檔案來說,可以利用上面的成員函式移動指標,隨機地訪問檔案中任一位置上的資料,還可以修改檔案中的內容。

[例13.16] 有個學生的資料,要求:
  • 把它們存到磁碟檔案中;
  • 將磁碟檔案中的第,3,5個學生資料讀入程式,並顯示出來;
  • 將第個學生的資料修改後存回磁碟檔案中的原有位置。
  • 從磁碟檔案讀入修改後的個學生的資料並顯示出來。

要實現以上要求,需要解決個問題:
  • 由於同一磁碟檔案在程式中需要頻繁地進行輸入和輸出,因此可將檔案的工作方式指定為輸入輸出檔案,即ios::in|ios::out|ios::binary。
  • 正確計算好每次訪問時指標的定位,即正確使用seekg或seekp函式。
  • 正確進行檔案中資料的重寫(更新)。

可寫出以下程式:
  1. #include <fstream>
  2. using namespace std;
  3. structstudent
  4. {
  5. int num;
  6. char name[20];
  7. float score;
  8. };
  9. int main( )
  10. {
  11. studentstud[5]={1001,"Li",85,1002,"Fun",97.5,1004,"Wang",54,1006,"Tan",76.5,1010,"ling",96};
  12. fstreamiofile("stud.dat",ios::in|ios::out|ios::binary);
  13. //用fstream類定義輸入輸出二進位制檔案流物件iofile
  14. if(!iofile)
  15. {
  16. cerr<<"open error!"<<endl;
  17. abort( );
  18. }
  19. for(int i=0;i<5;i++) //向磁碟檔案輸出個學生的資料
  20. iofile.write((char *)&stud[i],sizeof(stud[i]));
  21. studentstud1[5]; //用來存放從磁碟檔案讀入的資料
  22. for(int i=0;i<5;i=i+2)
  23. {
  24. iofile.seekg(i*sizeof(stud[i]),ios::beg); //定位於第,2,4學生資料開頭
  25. //先後讀入個學生的資料,存放在stud1[0],stud[1]和stud[2]中
  26. iofile.read((char *)&stud1[i/2],sizeof(stud1[0]));
  27. //輸出stud1[0],stud[1]和stud[2]各成員的值
  28. cout<<stud1[i/2].num<<" "<<stud1[i/2].name<<" "<<stud1[i/2].score<<endl;
  29. }
  30. cout<<endl;
  31. stud[2].num=1012; //修改第個學生(序號為)的資料
  32. strcpy(stud[2].name,"Wu");
  33. stud[