fstream,istream,ofstream 三個類之間的繼承關系
fstream (fstream繼承自istream和ofstream)
1.typedef basic_fstream<char, char_traits<char> > fstream;// 可以看出fstream就是basic_fstream
2.template<class _Elem,class _Traits> class basic_fstream: publicbasic_iostream<_Elem, _Traits>
3.template<class _Elem,class _Traits>class basic_iostream: publicbasic_istream<_Elem, _Traits>,publicbasic_ostream<_Elem, _Traits>
istream
1.typedef basic_ifstream<char, char_traits<char> > ifstream;// basic_ifstream就是istream
2.template<class _Elem,class _Traits>class basic_ifstream : publicbasic_istream<_Elem, _Traits> // basic_istream 其實就是 istream
3.template<class _Elem,class _Traits>class basic_istream : virtual publicbasic_ios<_Elem, _Traits>
4.template<class _Elem,class _Traits>class basic_ios : public ios_base
5.class _CRTIMP2_PURE ios_base : public _Iosb<int>// 最終父類
ofstream
1.typedef basic_ofstream<char, char_traits<char> > ofstream;
2.template<class _Elem,class _Traits>class basic_ofstream : publicbasic_ostream<_Elem, _Traits> // basic_ostream 其實就是 ostream
3.template<class _Elem,class _Traits>class basic_ostream: virtual publicbasic_ios<_Elem, _Traits>
這裡順道也把STL中其他幾個iostreams 提一句
iostream
1.typedef basic_iostream<char, char_traits<char> > iostream;
2.template<class _Elem,class _Traits>class basic_iostream: publicbasic_istream<_Elem, _Traits>,publicbasic_ostream<_Elem, _Traits>
3.basic_ostream/basic_istream 都繼承自basic_ios
strstream // 繼承自 iostream
streambuf // 繼承自 basic_streambuf
stringstream // 繼承自 basic_stringstream
istringstream // 繼承自 basic_istringstream
ostringstream // 繼承自 basic_istringstream
fstream 中seekg和seekp是關聯的移動讀指針寫指針随之移動移動寫指針讀指針也會随之移動。
為了搞清楚具體是怎麼關聯的編了個小程式測試了一下
int main()
{
fstream f("seekpg",ios::in|ios::out);
f << "aaaaaa" << endl;
int pp = f.tellp();
int pg = f.tellg();
cout << "pp=" << pp << "\t" << "pg="<<pg << endl;
f << "bbbbbbb" << endl;;
pp = f.tellp();
pg = f.tellg();
f.seekg(3,ios::beg);
f.seekp(9,ios::beg);
f << "cccccc" << endl;
return 0;
}
程式輸出
pp=7 pg=7
pp=15 pg=15
pp=3 pg=3
pp=16 pg=16
從結果可以發現
1确實seekg影響到了寫操作seekp也影響到了讀操作seekg和seekp是關聯的
2tellg和tellp的值始終是一樣的。
問fstream中真有所謂的讀指針和寫指針嗎如果有他們是一個東西嗎
答實際上我們知道fstream繼承自ifstream和ofstream是他們倆的子類 而seekp和tellp是ofstream的成員函數,seekg和tellg是ifstream的成員函數seekp是指seek putseekg是指seek get。之是以在fstream中他們相同是因為這裡指定打開方式 in | out
C++ 通過以下幾個類支援檔案的輸入輸出
ofstream: 寫操作輸出的檔案類 (由ostream引申而來)
ifstream: 讀操作輸入的檔案類(由istream引申而來)
fstream: 可同時讀寫操作的檔案類 (由iostream引申而來)
對這些類的一個對象所做的第一個操作通常就是将它和一個真正的檔案聯系起來也就是說打開一個檔案。被打開的檔案在程式中由一個流對象(stream object)來表示 (這些類的一個執行個體) 而對這個流對象所做的任何輸入輸出操作實際就是對該檔案所做的操作。
要通過一個流對象打開一個檔案我們使用它的成員函數open()
<code>void open (const char * filename, openmode mode);</code>
這裡filename 是一個字元串代表要打開的檔案名mode 是以下标志符的一個組合
這些辨別符可以被組合使用中間以”或”操作符(|)間隔。例如如果我們想要以二進制方式打開檔案"example.bin" 來寫入一些資料我們可以通過以下方式調用成員函數open來實作
ios::in
為輸入(讀)而打開檔案
ios::out
為輸出(寫)而打開檔案
ios::ate
初始位置檔案尾
ios::app
所有輸出附加在檔案末尾
ios::trunc
如果檔案已存在則先删除該檔案内容
ios::binary
二進制方式
ios::nocreate
不建立檔案是以檔案不存在時打開失敗
ios::noreplace
不覆寫檔案是以打開檔案時如果檔案存在失敗
<code>ofstream file; file.open ("example.bin", ios::out | ios::app | ios::binary);</code>
ofstream, ifstream 和 fstream所有這些類的成員函數open 都包含了一個預設打開檔案的方式這三個類的預設方式各不相同
類
參數的預設方式
ios::out | ios::trunc
ifstream
fstream
ios::in | ios::out
ofstream打開檔案不存在預設會建立這個檔案。除非指定ios::nocreate
ifstream打開檔案存在與否預設不會建立在個檔案.
fstream打開檔案不存在預設會建立這個檔案。除非隻是指定ios::in 或者指定ios::nocreate
隻有當函數被調用時沒有聲明方式參數的情況下預設值才會被采用。如果函數被調用時聲明了任何參數預設值将被完全改寫而不會與調用參數組合。
由于對類ofstream, ifstream 和 fstream 的對象所進行的第一個操作通常都是打開檔案這些類都有一個構造函數可以直接調用open 函數并擁有同樣的參數。這樣我們就可以通過以下方式進行與上面同樣的定義對象和打開檔案的操作
<code>ofstream file ("example.bin", ios::out | ios::app | ios::binary);</code>
兩種打開檔案的方式都是正确的。
你可以通過調用成員函數is_open()來檢查一個檔案是否已經被順利的打開了
<code>bool is_open();</code>
它傳回一個布爾(bool)值為真true代表檔案已經被順利打開假( false )則相反。
當檔案讀寫操作完成之後我們必須将檔案關閉以使檔案重新變為可通路的。關閉檔案需要調用成員函數close()它負責将緩存中的資料排放出來并關閉檔案。它的格式很簡單
<code>void close ();</code>
這個函數一旦被調用原先的流對象(stream object)就可以被用來打開其它的檔案了這個檔案也就可以重新被其它的程序(process)所有通路了。
為防止流對象被銷毀時還聯系着打開的檔案析構函數(destructor)将會自動調用關閉函數close。
類ofstream, ifstream 和fstream 是分别從ostream, istream 和iostream 中引申而來的。這就是為什麼 fstream 的對象可以使用其父類的成員來通路資料。
一般來說我們将使用這些類與同控制台(console)互動同樣的成員函數(cin 和 cout)來進行輸入輸出。如下面的例題所示我們使用重載的插入操作符<<
file example.txt
This is a line.
This is another line.
從檔案中讀入資料也可以用與 cin的使用同樣的方法
上面的例子讀入一個文本檔案的内容然後将它列印到螢幕上。注意我們使用了一個新的成員函數叫做eof 它是ifstream 從類 ios 中繼承過來的當到達檔案末尾時傳回true 。
除了eof()以外還有一些驗證流的狀态的成員函數所有都傳回bool型傳回值
bad()
如果在讀寫過程中出錯傳回 true 。例如當我們要對一個不是打開為寫狀态的檔案進行寫入時或者我們要寫入的裝置沒有剩餘空間的時候。
fail()
除了與bad() 同樣的情況下會傳回 true 以外加上格式錯誤時也傳回true 例如當想要讀入一個整數而獲得了一個字母的時候。
eof()
如果讀檔案到達檔案末尾傳回true。
good()
這是最通用的如果調用以上任何一個函數傳回true 的話此函數傳回 false 。
要想重置以上成員函數所檢查的狀态标志你可以使用成員函數clear()沒有參數。
所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針
ifstream 類似istream, 有一個被稱為get pointer的指針指向下一個将被讀取的元素。
ofstream, 類似 ostream, 有一個指針 put pointer 指向寫入下一個元素的位置。
fstream, 類似 iostream, 同時繼承了get 和 put
我們可以通過使用以下成員函數來讀出或配置這些指向流中讀寫位置的流指針
tellg() 和 tellp()
這兩個成員函數不用傳入參數傳回pos_type 類型的值(根據ANSI-C++ 标準) 就是一個整數代表目前get 流指針的位置 (用tellg) 或 put 流指針的位置(用tellp).
seekg() 和seekp()
這對函數分别用來改變流指針get 和put的位置。兩個函數都被重載為兩種不同的原型
seekg ( pos_type position );
seekp ( pos_type position );
使用這個原型流指針被改變為指向從檔案開始計算的一個絕對位置。要求傳入的參數類型與函數 tellg 和tellp 的傳回值類型相同。
seekg ( off_type offset, seekdir direction );
seekp ( off_type offset, seekdir direction );
使用這個原型可以指定由參數direction決定的一個具體的指針開始計算的一個位移(offset)。它可以是
ios::beg
從流開始位置計算的位移
ios::cur
從流指針目前位置開始計算的位移
ios::end
從流末尾處開始計算的位移
流指針 get 和 put 的值對文本檔案(text file)和二進制檔案(binary file)的計算方法都是不同的因為文本模式的檔案中某些特殊字元可能被修改。由于這個原因建議對以文本檔案模式打開的檔案總是使用seekg 和 seekp的第一種原型而且不要對tellg 或 tellp 的傳回值進行修改。對二進制檔案你可以任意使用這些函數應該不會有任何意外的行為産生。
以下例子使用這些函數來獲得一個二進制檔案的大小
size of example.txt is 40 bytes.
在二進制檔案中使用<< 和>>以及函數如getline來操作符輸入和輸出資料沒有什麼實際意義雖然它們是符合文法的。
檔案流包括兩個為順序讀寫資料特殊設計的成員函數write 和 read。第一個函數 (write) 是ostream 的一個成員函數都是被ofstream所繼承。而read 是istream 的一個成員函數被ifstream 所繼承。類 fstream 的對象同時擁有這兩個函數。它們的原型是
write ( char * buffer, streamsize size );
read ( char * buffer, streamsize size );
這裡 buffer 是一塊記憶體的位址用來存儲或讀出資料。參數size 是一個整數值表示要從緩存buffer中讀出或寫入的字元數。
The complete file is in a buffer
當我們對檔案流進行操作的時候它們與一個streambuf 類型的緩存(buffer)聯系在一起。這個緩存buffer實際是一塊記憶體空間作為流(stream)和實體檔案的媒介。例如對于一個輸出流 每次成員函數put (寫一個單個字元)被調用這個字元不是直接被寫入該輸出流所對應的實體檔案中的而是首先被插入到該流的緩存buffer中。
當緩存被排放出來(flush)時它裡面的所有資料或者被寫入實體媒質中如果是一個輸出流的話或者簡單的被抹掉(如果是一個輸入流的話)。這個過程稱為同步(synchronization)它會在以下任一情況下發生
當檔案被關閉時: 在檔案被關閉之前所有還沒有被完全寫出或讀取的緩存都将被同步。
當緩存buffer 滿時:緩存Buffers 有一定的空間限制。當緩存滿時它會被自動同步。
控制符明确指明:當遇到流中某些特定的控制符時同步會發生。這些控制符包括flush 和endl。
明确調用函數sync(): 調用成員函數sync() (無參數)可以引發立即同步。這個函數傳回一個int 值等于-1 表示流沒有聯系的緩存或操作失敗。
在C++中有一個stream這個類所有的I/O都以這個“流”類為基礎的包括我們要認識的檔案I/Ostream這個類有兩個重要的運算符
1、插入器(<<)
向流輸出資料。比如說系統有一個預設的标準輸出流(cout)一般情況下就是指的顯示器是以cout<<"Write Stdout"<<'n';就表示把字元串"Write Stdout"和換行字元('n')輸出到标準輸出流。
2、析取器(>>)
從流中輸入資料。比如說系統有一個預設的标準輸入流(cin)一般情況下就是指的鍵盤是以cin>>x;就表示從标準輸入流中讀取一個指定類型(即變量x的類型)的資料。
在C++中對檔案的操作是通過stream的子類fstream(file stream)來實作的是以要用這種方式操作檔案就必須加入頭檔案fstream.h。下面就把此類的檔案操作過程一一道來。
一、打開檔案
在fstream類中有一個成員函數open()就是用來打開檔案的其原型是
void open(const char* filename,int mode,int access);
參數
filename 要打開的檔案名
mode 要打開檔案的方式
access 打開檔案的屬性
打開檔案的方式在類ios(是所有流式I/O類的基類)中定義常用的值如下
ios::app 以追加的方式打開檔案
ios::ate 檔案打開後定位到檔案尾ios:app就包含有此屬性
ios::binary 以二進制方式打開檔案預設的方式是文本方式。兩種方式的差別見前文
ios::in 檔案以輸入方式打開
ios::out 檔案以輸出方式打開
ios::nocreate 不建立檔案是以檔案不存在時打開失敗
ios::noreplace不覆寫檔案是以打開檔案時如果檔案存在失敗
ios::trunc 如果檔案存在把檔案長度設為0
可以用“或”把以上屬性連接配接起來如ios::out|ios::binary
打開檔案的屬性取值是
0普通檔案打開通路
1隻讀檔案
2隐含檔案
4系統檔案
可以用“或”或者“+”把以上屬性連接配接起來 如3或1|2就是以隻讀和隐含屬性打開檔案。
例如以二進制輸入方式打開檔案c:config.sys
fstream file1;
file1.open("c:config.sys",ios::binary|ios::in,0);
如果open函數隻有檔案名一個參數則是以讀/寫普通檔案打開即
file1.open("c:config.sys");<=>file1.open("c:config.sys",ios::in|ios::out,0);
另外fstream還有和open()一樣的構造函數對于上例在定義的時侯就可以打開檔案了
fstream file1("c:config.sys");
特别提出的是fstream有兩個子類ifstream(input file stream)和ofstream(outpu file stream)ifstream預設以輸入方式打開檔案而ofstream預設以輸出方式打開檔案。
ifstream file2("c:pdos.def");//以輸入方式打開檔案
ofstream file3("c:x.123");//以輸出方式打開檔案
是以在實際應用中根據需要的不同選擇不同的類來定義如果想以輸入方式打開就用ifstream來定義如果想以輸出方式打開就用ofstream來定義如果想以輸入/輸出方式來打開就用fstream來定義。
二、關閉檔案
打開的檔案使用完成後一定要關閉fstream提供了成員函數close()來完成此操作如file1.close();就把file1相連的檔案關閉。
三、讀寫檔案
讀寫檔案分為文本檔案和二進制檔案的讀取對于文本檔案的讀取比較簡單用插入器和析取器就可以了而對于二進制的讀取就要複雜些下要就詳細的介紹這兩種方式
1、文本檔案的讀寫
文本檔案的讀寫很簡單用插入器(<<)向檔案輸出用析取器(>>)從檔案輸入。假設file1是以輸入方式打開file2以輸出打開。示例如下
file2<<"I Love You";//向檔案寫入字元串"I Love You"
int i;
file1>>i;//從檔案輸入一個整數值。
這種方式還有一種簡單的格式化能力比如可以指定輸出為16進制等等具體的格式有以下一些
操縱符 功能 輸入/輸出
dec 格式化為十進制數值資料 輸入和輸出
endl 輸出一個換行符并重新整理此流 輸出
ends 輸出一個空字元 輸出
hex 格式化為十六進制數值資料 輸入和輸出
oct 格式化為八進制數值資料 輸入和輸出
setpxecision(int p) 設定浮點數的精度位數 輸出
比如要把123當作十六進制輸出file1<<hex<<123;要把3.1415926以5位精度輸出file1<<setpxecision(5)<<3.1415926。
2、二進制檔案的讀寫
①put()
put()函數向流寫入一個字元其原型是ofstream &put(char ch)使用也比較簡單如file1.put('c');就是向流寫一個字元'c'。
②get()
get()函數比較靈活有3種常用的重載形式
一種就是和put()對應的形式ifstream &get(char &ch);功能是從流中讀取一個字元結果儲存在引用ch中如果到檔案尾傳回空字元。如file2.get(x);表示從檔案中讀取一個字元并把讀取的字元儲存在x中。
另一種重載形式的原型是 int get();這種形式是從流中傳回一個字元如果到達檔案尾傳回EOF如x=file2.get();和上例功能是一樣的。
還 有一種形式的原型是ifstream &get(char *buf,int num,char delim='n')這種形式把字元讀入由 buf 指向的數組直到讀入了 num 個字元或遇到了由 delim 指定的字元如果沒使用 delim 這個參數将使用預設值換行符'n'。例如
file2.get(str1,127,'A');//從檔案中讀取字元到字元串str1當遇到字元'A'或讀取了127個字元時終止。
③讀寫資料塊
要讀寫二進制資料塊使用成員函數read()和write()成員函數它們原型如下
read(unsigned char *buf,int num);
write(const unsigned char *buf,int num);
read() 從檔案中讀取 num 個字元到 buf 指向的緩存中如果在還未讀入 num 個字元時就到了檔案尾可以用成員函數 int gcount();來取得實際讀取的字元數而 write() 從buf 指向的緩存寫 num 個字元到檔案中值得注意的是緩存的類型是 unsigned char *有時可能需要類型轉換。
例
unsigned char str1[]="I Love You";
int n[5];
ifstream in("xxx.xxx");
ofstream out("yyy.yyy");
out.write(str1,strlen(str1));//把字元串str1全部寫到yyy.yyy中
in.read((unsigned char*)n,sizeof(n));//從xxx.xxx中讀取指定個整數注意類型轉換
in.close();out.close();
四、檢測EOF
成員函數eof()用來檢測是否到達檔案尾如果到達檔案尾傳回非0值否則傳回0。原型是int eof();
例 if(in.eof())ShowMessage("已經到達檔案尾");
五、檔案定位
和C的檔案操作方式不同的是C++ I/O系統管理兩個與一個檔案相聯系的指針。一個是讀指針它說明輸入操作在檔案中的位置另一個是寫指針它下次寫操作的位置。每次執行輸入或輸出時 相應的指針自動變化。是以C++的檔案定位分為讀位置和寫位置的定位對應的成員函數是 seekg()和 seekp()seekg()是設定讀位置seekp是設定寫位置。它們最通用的形式如下
istream &seekg(streamoff offset,seek_dir origin);
ostream &seekp(streamoff offset,seek_dir origin);
streamoff定義于 iostream.h 中定義有偏移量 offset 所能取得的最大值seek_dir 表示移動的基準位置是一個有以下值的枚舉
ios::beg 檔案開頭
ios::cur 檔案目前位置
ios::end 檔案結尾
這兩個函數一般用于二進制檔案因為文本檔案會因為系統對字元的解釋而可能與預想的值不同。
file1.seekg(1234,ios::cur);//把檔案的讀指針從目前位置向後移1234個位元組
file2.seekp(1234,ios::beg);//把檔案的寫指針從檔案開頭向後移1234個位元組