前兩節介紹了C++的IO流類庫,標准設備IO操作流中部分預定義流對象的成員函數以及IO格式控制。那今天我將繼續介紹關於C++中的流操作內容——文件IO操作流fstream。並會著重講解C++是如何對文件進行操作的。
文件指存放在外部介質上的數據的集合。大家都知道操作系統是以文件為單位來對數據進行管理的。因此如果你要查找外部介質的數據,則先要按文件名找到指定文件,然後再從文件中讀取數據,如果要把數據存入外部介質中,如果沒有該文件,則先要建立文件,再向它輸入數據。由於文件的內容千變萬化,大小各不相同,為了統一處理,在C++中用文件流的形式來處理,文件流是以外存文件為輸入輸出對象的數據流。輸出文件流表示從內存流向外存文件的數據,輸入文件流則相反。根據文件中數據的組織形式,文件可分為兩類:文本文件和二進制文件。文本文件又稱為ASCII文件,它的每個字節存放一個ASCII碼,代表一個字符。二進制文件則是把內存中的數據,按照其在內存中的存儲形式原樣寫在磁盤上存放。比如一個整數20000,在內存中在兩個字節,而按文本形式輸出則占5個字節。因此在以文本形式輸出時,一個字節對應一個字符,因而便於字符的輸出,缺點則是占用存儲空間較多。用二進制形式輸出數據,節省了轉化時間和存儲空間,但不能直接以字符的形式輸出。
1.在C++中對文件進行操作分為以下幾個步驟:(1)建立文件流對象;(2)打開或建立文件;(3)進行讀寫操作;(4)關閉文件;用於文件IO操作的流類主要有三個fstream(輸入輸出文件流),ifstream(輸入文件流)和ofstream(輸出文件流);而這三個類都包含在頭文件fstream中,所以程序中對文件進行操作必須包含該頭文件。首先建立流對象,然後使用文件流類的成員函數open打開文件,即把文件流對象和指定的磁盤文件建立關聯。成員函數open的一般形式為:
文件流對象.open(文件名,使用方式);
其中文件名可以包括路徑(如:e:\c++\file.txt),如果缺少路徑,則默認為當前目錄。使用方式則是指文件將被如何打開。以下就是文件的部分使用方式,都是ios基類中的枚舉類型的值:
此外打開方式有幾個注意點:
(1)因為nocreate和noreplace,與系統平台相關密切,所以在C++標准中去掉了對它的支持。
(2)每一個打開的文件都有一個文件指針,指針的開始位置由打開方式指定,每次讀寫都從文件指針的當前位置開始。每讀一個字節,指針就後移一個字節。當文件指針移到最後,會遇到文件結束符EOF,此時流對象的成員函數eof的值為非0值,表示文件結束。
(3)用in方式打開文件只能用於輸入數據,而且該文件必須已經存在。
(4)用app方式打開文件,此時文件必須存在,打開時文件指針處於末尾,且該方式只能用於輸出。
(5)用ate方式打開一個已存在的文件,文件指針自動移到文件末尾,數據可以寫入到其中。
如果文件需要用兩種或多種方式打開,則用"|"來分隔組合在一起。除了用open成員函數打開文件,還可以用文件流類的構造函數來打開文件,其參數和默認值與open函數完全相同。比如:文件流類 stream(文件名,使用方法);如果文件打開操作失敗,open函數的返回值為0,用構造函數打開的話,流對象的值為0。所以無論用哪一種方式打開文件,都需要在程序中測試文件是否成功打開。
在每次對文件IO操作結束後,都需要把文件關閉,那麼就需要用到文件流類的成員函數close,一般調用形式:流對象.close();關閉實際上就是文件流對象和磁盤文件失去關聯。
2.介紹完文件的打開和關閉,接下來說說文件的讀寫。我將分別從文本文件讀寫和二進制文件的讀寫來介紹。其實文件的讀寫是十分容易的。流類庫中的IO操作<<、>>、put、get、getline、read和write都可以用於文件的輸入輸出。
(1)文本文件的讀寫:
寫文件:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <fstream>
4
5 int main()
6 {
7 //打開文件
8 std::ofstream file("file.txt",std::ios::out|std::ios::ate);
9 if(!file)
10 {
11 std::cout<<"不可以打開文件"<<std::endl;
12 exit(1);
13 }
14
15 //寫文件
16 file<<"hello c++!\n";
17
18 char ch;
19 while(std::cin.get(ch))
20 {
21 if(ch=='\n')
22 break;
23 file.put(ch);
24 }
25
26 //關閉文件
27 file.close();
28
29 return 0;
30 }
鍵盤輸入字符:
讀文件file.txt:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <fstream>
4
5 int main()
6 {
7 //打開文件
8 std::ifstream rfile("file.txt",std::ios::in);
9 if(!rfile)
10 {
11 std::cout<<"不可以打開文件"<<std::endl;
12 exit(1);
13 }
14
15 //讀文件
16 char str[100];
17 rfile.getline(str,100);//讀到'\n'終止
18 std::cout<<str<<std::endl;
19
20 char rch;
21 while(rfile.get(rch))//文件指針指向字符‘\n’的下一個
22 {
23 std::cout.put(rch);
24 }
25
26 std::cout<<std::endl;
27
28 //關閉文件
29 rfile.close();
30
31 return 0;
32 }
讀出顯示字符:
其實建立ifstream類和ofstream類的對象時,ios:in和ios:out可以省略,因為ifstream類默認為ios:in,ofstream類默認為ios:out;
(2)最初設計流的目的是用於文本,因此在默認情況下,文件用文本方式打開。在以文本模式輸出時,若遇到換行符"\n"(十進制為10)則自動擴充為回車換行符(十進制為13和10)。所以,如果我們輸入的整數10,那麼在文件輸出時會轉化為13和10,然而這並不是我們所需要的。為了解決這樣的問題,就要采用而二進制模式,使其所寫的字符不轉換。在對二進制文件進行IO操作時,打開文件時要指定方式ios::binary,即以二進制形式傳送和存儲。接下來我用read函數和write函數來對二進制文件進行讀寫。在示例描述之前先簡單介紹一下這兩個函數:
read函數常用格式為:文件流對象.read(char *buf,int len);
write函數常用格式為:文件流對象.write(const char *buf,int len);
兩者格式上差不多,第一個參數是一個字符指針,用於指向讀入讀出數據所放的內存空間的其實地址。第二個參數是一個整數,表示要讀入讀出的數據的字節數。以下是二進制文件的讀寫的示例:
定義一個精靈類(用於文件數據處理):
1 class Sprite
2 {
3 private:
4 std::string profession;//職業
5 std::string weapon;//武器
6 static int count;//個數
7 public:
8 Sprite(){}
9 Sprite(std::string profession,std::string weapon):profession(profession),weapon(weapon)
10 {
11 }
12 void showSprite();//顯示精靈信息
13 };
14
15 int Sprite::count=0;
16
17 void Sprite::showSprite()
18 {
19 ++count;
20 std::cout<<"精靈"<<count<<" 職業:"<<profession<<" 武器:"<<weapon<<std::endl;
21 }
寫文件:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <fstream>
4 #include <string>
5
6 int main()
7 {
8 //建立對象數組
9 Sprite sprites[3]={
10 Sprite("法師","魔杖"),
11 Sprite("戰士","屠龍寶刀"),
12 Sprite("道士","倚天劍")
13 };
14
15 //打開文件
16 std::ofstream file("file.dat",std::ios::ate|std::ios::binary);
17 if(!file)
18 {
19 std::cout<<"文件打開失敗!";
20 abort();//等同於exit
21 }
22
23 //寫文件
24 for(int i=0;i<3;i++)
25 file.write((char*) &sprites[i],sizeof(sprites[i]));
26
27 //關閉文件
28 file.close();
29
30 return 0;
31 }
讀文件file.dat:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <fstream>
4 #include <string>
5
6 int main()
7 {
8 //建立對象數組
9 Sprite rsprites[3];
10
11 //打開文件
12 std::ifstream rfile("file.dat",std::ios::binary);
13 if(!rfile)
14 {
15 std::cout<<"文件打開失敗!";
16 return 1;//等同於exit
17 }
18
19 //讀文件
20 for(int i=0;i<3;i++)
21 {
22 rfile.read((char*) &rsprites[i],sizeof(rsprites[i]));
23 rsprites[i].showSprite();
24 }
25
26 //關閉文件
27 rfile.close();
28
29 return 0;
30 }
讀出顯示字符:
在read函數還是write函數裡都要把數據轉化為char*類型,代碼中sizeof函數是用於確定要讀入讀出的字節數。
在文件結束處有個標志位EOF,在用文件流讀取文件時,使用成員函數eof()(函數原型:int eof())可以檢測到結束符。如果該函數返回值為非零,則表示到達文件末尾。返回零則表示未達到文件末尾。
(3)前面所介紹的文件都是按順序來讀取的的,C++中又提供了針對於文件讀寫指針的相關成員函數,使得我們可以在IO流中隨意移動文件指針,從而對文件的進行隨機地讀寫。類istream針對讀指針提供3個成員函數:
tellg()//返回輸入文件讀指針的當前位置;
seekg(文件中的位置)//將輸入文件中的讀指針移動到指定位置
seekg(位移量,參照位置)//以參照位置為基准移動若干字節
其中參照位置是枚舉值:
beg//從文件開頭計算要移動的字節數
cur//從文件指針的當前位置計算要移動的字節數
end//從文件的末尾計算要移動的字節數
如果參照位置省略,則默認為beg。而類ostream針對寫指針提供的3個成員函數:
tellp()//返回輸出文件寫指針的當前位置;
seekp(文件中的位置)//將輸出文件中的寫指針移動到指定位置
seekp(位移量,參照位置)//以參照位置為基准移動若干字節
現在我對上一示例中讀取二進制文件代碼稍作更改:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <fstream>
4 #include <string>
5
6 int main()
7 {
8 //建立對象數組
9 Sprite rsprites[3];
10
11 //打開文件
12 std::ifstream rfile("file.dat",std::ios::binary);
13 if(!rfile)
14 {
15 std::cout<<"文件打開失敗!";
16 return 1;//等同於exit
17 }
18
19 //讀文件
20 for(int i=0;i<3;i++)
21 {
22 rfile.read((char*) &rsprites[i],sizeof(rsprites[i]));
23 rsprites[i].showSprite();
24 }
25
26 Sprite rsprite;//建立對象
27
28 std::cout<<"改變讀取順序:"<<std::endl;
29 rfile.seekg(sizeof(Sprite)*2,std::ios::beg);//讀取精靈道士信息
30 rfile.read((char*) &rsprite,sizeof(Sprite));
31 rsprite.showSprite();
32
33 rfile.seekg(-int(sizeof(Sprite)*2),std::ios::end);//讀取精靈戰士信息
34 rfile.read((char*) &rsprite,sizeof(Sprite));
35 rsprite.showSprite();
36
37 rfile.seekg(-int(sizeof(Sprite)*2),std::ios::cur);//讀取精靈法師信息
38 rfile.read((char*) &rsprite,sizeof(Sprite));
39 rsprite.showSprite();
40
41 //關閉文件
42 rfile.close();
43
44 return 0;
45 }
結果: