在看C++編程思想中,每個練習基本都是使用ofstream,ifstream,fstream,以前粗略知道其用法和含義,在看了幾位大牛的博文後,進行整理和總結:
這裏主要是討論fstream的內容:
- #include <fstream>
- ofstream //文件寫操作 內存寫入存儲設備
- ifstream //文件讀操作,存儲設備讀取到內存中
- fstream //讀寫操作,對打開的文件可進行讀寫操作
1.打開文件
在fstream類中,成員函數open()實現打開文件的操作,從而將數據流和文件進行關聯,通過ofstream,ifstream,fstream對象進行對文件的讀寫操作
函數:open()
- void open ( const char * filename,
- ios_base::openmode mode = ios_base::in | ios_base::out );
-
- void open(const wchar_t *_Filename,
- ios_base::openmode mode= ios_base::in | ios_base::out,
- int prot = ios_base::_Openprot);
參數: filename 操作文件名
mode 打開文件的方式
prot 打開文件的屬性 //基本很少用到,在查看資料時,發現有兩種方式
打開文件的方式在ios類(所以流式I/O的基類)中定義,有如下幾種方式:
ios::in | 爲輸入(讀)而打開文件 |
ios::out | 爲輸出(寫)而打開文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有輸出附加在文件末尾 |
ios::trunc | 如果文件已存在則先刪除該文件 |
ios::binary | 二進制方式 |
- ofstream out;
- out.open("Hello.txt", ios::in|ios::out|ios::binary) //根據自己需要進行適當的選取
0 | 普通文件,打開操作 |
1 | 只讀文件 |
2 | 隱含文件 |
4 | 系統文件 |
很多程序中,可能會碰到ofstream out("Hello.txt"), ifstream in("..."),fstream foi("...")這樣的的使用,並沒有顯式的去調用open()函數就進行文件的操作,直接調用了其默認的打開方式,因爲在stream類的構造函數中調用了open()函數,並擁有同樣的構造函數,所以在這裏可以直接使用流對象進行文件的操作,默認方式如下:
- ofstream out("...", ios::out);
- ifstream in("...", ios::in);
- fstream foi("...", ios::in|ios::out);
ofstream:打開文件不存在,默認會創建這個文件。(除非指定ios::nocreate)
ifstream:打開文件存在與否,默認不會創建在個文件.
fstream:打開文件不存在,默認會創建這個文件。(除非只是指定ios::in 或者指定ios::nocreate)
當使用默認方式進行對文件的操作時,你可以使用成員函數is_open()對文件是否打開進行驗證
2.關閉文件
當文件讀寫操作完成之後,我們必須將文件關閉以使文件重新變爲可訪問的。成員函數close(),它負責將緩存中的數據排放出來並關閉文件。這個函數一旦被調用,原先的流對象就可以被用來打開其它的文件了,這個文件也就可以重新被其它的進程所訪問了。爲防止流對象被銷燬時還聯繫着打開的文件,析構函數將會自動調用關閉函數close。
3.文本文件的讀寫
類ofstream, ifstream 和fstream 是分別從ostream, istream 和iostream 中引申而來的。這就是爲什麼 fstream 的對象可以使用其父類的成員來訪問數據。
一般來說,我們將使用這些類與同控制檯(console)交互同樣的成員函數(cin 和 cout)來進行輸入輸出。如下面的例題所示,我們使用重載的插入操作符<<:
- // writing on a text file
- #include <fiostream.h>
- int main ()
- {
- ofstream out("out.txt");
- if (out.is_open())
- {
- out << "This is a line.\n";
- out << "This is another line.\n";
- out.close();
- }
- return 0;
- }
- //結果: 在out.txt中寫入:
- This is a line.
- This is another line
從文件中讀入數據也可以用與 cin>>的使用同樣的方法:
- // reading a text file
- #include <iostream.h>
- #include <fstream.h>
- #include <stdlib.h>
-
- int main () {
- char buffer[256];
- ifstream in("test.txt");
- if (! in.is_open())
- { cout << "Error opening file"; exit (1); }
- while (!in.eof() )
- {
- in.getline (buffer,100);
- cout << buffer << endl;
- }
- return 0;
- }
- //結果 在屏幕上輸出
- This is a line.
- This is another line
- dec 格式化爲十進制數值數據 輸入和輸出
- endl 輸出一個換行符並刷新此流 輸出
- ends 輸出一個空字符 輸出
- hex 格式化爲十六進制數值數據 輸入和輸出
- oct 格式化爲八進制數值數據 輸入和輸出
- setpxecision(int p) 設置浮點數的精度位數 輸出
上面的例子讀入一個文本文件的內容,然後將它打印到屏幕上。注意我們使用了一個新的成員函數叫做eof ,它是ifstream 從類 ios 中繼承過來的,當到達文件末尾時返回true 。
狀態標誌符的驗證(Verification of state flags)
除了eof()以外,還有一些驗證流的狀態的成員函數(所有都返回bool型返回值):
- bad()
如果在讀寫過程中出錯,返回 true 。例如:當我們要對一個不是打開爲寫狀態的文件進行寫入時,或者我們要寫入的設備沒有剩餘空間的時候。
- fail()
除了與bad() 同樣的情況下會返回 true 以外,加上格式錯誤時也返回true ,例如當想要讀入一個整數,而獲得了一個字母的時候。
- eof()
如果讀文件到達文件末尾,返回true。
- good()
這是最通用的:如果調用以上任何一個函數返回true 的話,此函數返回 false 。
要想重置以上成員函數所檢查的狀態標誌,你可以使用成員函數clear(),沒有參數。
獲得和設置流指針(get and put stream pointers)
所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針:
- ifstream, 類似istream, 有一個被稱爲get pointer的指針,指向下一個將被讀取的元素。
- ofstream, 類似 ostream, 有一個指針 put pointer ,指向寫入下一個元素的位置。
- fstream, 類似 iostream, 同時繼承了get 和 put,fstream 中seekg和seekp是聯動的,移動讀指針,寫指針隨之移動,移動寫指針,讀指針也會隨之移動。
我們可以通過使用以下成員函數來讀出或配置這些指向流中讀寫位置的流指針:
- 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 的返回值進行修改。對二進制文件,你可以任意使用這些函數,應該不會有任何意外的行爲產生。
以下例子使用這些函數來獲得一個二進制文件的大小:
- // obtaining file size
- #include <iostream.h>
- #include <fstream.h>
-
- const char * filename = "test.txt";
-
- int main () {
- long l,m;
- ifstream in(filename, ios::in|ios::binary);
- l = in.tellg();
- in.seekg (0, ios::end);
- m = in.tellg();
- in.close();
- cout << "size of " << filename;
- cout << " is " << (m-l) << " bytes.\n";
- return 0;
- }
-
- //結果:
- size of example.txt is 40 bytes.
4.二進制文件
在二進制文件中,使用<< 和>>,以及函數(如getline)來操作符輸入和輸出數據,沒有什麼實際意義,雖然它們是符合語法的。
①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個字符時終止。
③write 和 read
文件流包括兩個爲順序讀寫數據特殊設計的成員函數:write 和 read。第一個函數 (write) 是ostream 的一個成員函數,都是被ofstream所繼承。而read 是istream 的一個成員函數,被ifstream 所繼承。類 fstream 的對象同時擁有這兩個函數。它們的原型是:
read ( char * buffer, streamsize size );
這裏 buffer 是一塊內存的地址,用來存儲或讀出數據。參數size 是一個整數值,表示要從緩存(buffer)中讀出或寫入的字符數。
- // reading binary file
- #include <iostream>
- #include <fstream.h>
- const char * filename = "test.txt";
-
- int main () {
- char * buffer;
- long size;
- ifstream in (filename, ios::in|ios::binary|ios::ate);
- size = in.tellg();
- in.seekg (0, ios::beg);
- buffer = new char [size];
- in.read (buffer, size);
- in.close();
-
- cout << "the complete file is in a buffer";
-
- delete[] buffer;
- return 0;
- }
- //運行結果:
- The complete file is in a buffer
5.緩存和同步(Buffers and Synchronization)
當我們對文件流進行操作的時候,它們與一個streambuf 類型的緩存(buffer)聯繫在一起。這個緩存(buffer)實際是一塊內存空間,作爲流(stream)和物理文件的媒介。例如,對於一個輸出流, 每次成員函數put (寫一個單個字符)被調用,這個字符不是直接被寫入該輸出流所對應的物理文件中的,而是首先被插入到該流的緩存(buffer)中。
當緩存被排放出來(flush)時,它裏面的所有數據或者被寫入物理媒質中(如果是一個輸出流的話),或者簡單的被抹掉(如果是一個輸入流的話)。這個過程稱爲同步(synchronization),它會在以下任一情況下發生:
- 當文件被關閉時: 在文件被關閉之前,所有還沒有被完全寫出或讀取的緩存都將被同步。
- 當緩存buffer 滿時:緩存Buffers 有一定的空間限制。當緩存滿時,它會被自動同步。
- 控制符明確指明:當遇到流中某些特定的控制符時,同步會發生。這些控制符包括:flush 和endl。
- 明確調用函數sync(): 調用成員函數sync() (無參數)可以引發立即同步。這個函數返回一個int 值,等於-1 表示流沒有聯繫的緩存或操作失敗。
步驟1:添加頭文件 #include <afx.h>
步驟2:定義CFile類的對象
格式:文件流類型 文件流對象名
- //定義輸入/輸出文件流對象,可讀可寫
- CFile iofile;
步驟3:打開要操作的文件
格式:文件流對象名.Open( 文件對象, 打開方式 );
- //打開當前工作空間下的名爲FileName的文件,如txtfile.txt、wavfile.wav
- iofile.Open( "FileName", 打開方式 );
- //打開絕對路徑FilePath下的文件,如D:\\txtfile.txt。注:必須路徑是雙反斜槓
- iofile.Open( "FilePath", 打開方式 );
- //以輸入方式打開文件,只讀。前提是文件必須存在
- iofile.Open( 文件對象, CFile::modeRead );
- //建立輸出方式文件,只寫。如存在此名字文件,則清除原有內容
- iofile.Open( 文件對象, CFile::modeCreate|CFile::modeWrite );
- //以輸入輸出方式打開文件,可讀可寫
- iofile.Open( 文件對象, CFile::modeWrite|CFile::modeRead );
- //建立輸出方式文件,只寫。如存在此名字文件,則保留原有內容
- iofile.Open( 文件對象, CFile::modeCreate|CFile::modeNoTruncate );
- //以二進制方式方式打開輸出文件
- iofile.Open( 文件對象, CFile::modeWrite|CFile::typeBinary );
步驟4:設置文件讀寫指針位置
格式:文件流對象名.GetPosition(); //獲得文件指針當前位置
格式:文件流對象名.SeekToBegin(); //將文件指針移到文件頭
格式:文件流對象名.SeekToEnd()(); //將文件指針移到文件尾
格式:文件流對象名.Seek( 位移量,參照位置值 ); //以參照位置爲基礎將文件指針移動位移量
- //將文件讀/寫指針移到距文件起始100字節位置
- iofile.Seek( 100, CFile::begin );
- //將文件讀/寫指針從當前位置向文件尾方向移50字節
- iofile.Seek( 50, CFile::current );
- //將文件讀/寫指針從當前位置向文件頭方向移50字節
- iofile.Seek( -50, CFile::current );
- //將文件讀/寫指針從文件尾回移50字節
- iofile.Seek( -50, CFile::end );
步驟5:對文件進行讀寫操作
格式:文件流對象名.Read/Write( 數據地址, 數據長度 );
- //從文件中讀取100字節數據按序從Rbuf地址開始存
- iofile.Read( &Rbuf, 100 );
- //將Wbuf地址開始的100字節數據按序寫入文件
- iofile.Write( &Wbuf, 100 );
步驟6:操作結束,關閉文件
格式:文件流對象名.Close();
備註
【1】當需要判斷文件讀寫是否到文件尾時,可聯合使用xxfile.GetPosition()函數與xxfile.GetLength()函數。
【2】在VC6.0下編譯程序無誤後,生成鏈接時報錯:
nafxcwd.lib(thrdcore.obj) : error LNK2001:unresolved external symbol __endthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
fatal error LNK1120: 2 unresolved externals
是因爲CFile屬於MFC類,非C++編譯系統提供,故需在軟件Project/ Setting/ General下將默認的Not Using MFC改爲Use MFC in a Static Library
CStdioFile類繼承自CFile類,CStdioFile對象表示一個C運行函數fopen打開的的流式文件。流式文件是被緩衝的,而且可以以文本方式(默認)或者二進制方式打開。
CStdioFile類不支持CFile類中的Duplicate、LockRange、UnlockRange函數,如果你使用了,會得到C Not Supported Exception類的錯誤。
CStringFile類默認的是按照Text模式操作文件。
CFile 類默認的是按照二進制模式操作文件。
成員變量
m_pStream成員變量:打開文件的指針。
構造函數
- CStdioFile();
-
- CStdioFile(FILE *pOpenStream);
-
- CStdioFile(LPCTSTR lpFileName, UINT nOpenFlags);
-
- throw(CFileException);
-
- FILE *pOpenStream:指的是c運行函數fopen調用後返回的文件指針。
-
- LPCTSTR lpFileName:指的是被打開的文件(絕對地址或相對地址)
-
- UINT nOpenFlags:指的是CFile類中所描述的打開文件的方式。
-
- //
讀函數
- virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax);
- throw(CFileException);
如果使用該函數讀取文本文件,當遇到'\r\n',停止讀取,並去掉'\r',保留'\n',並在字符串尾部增加'\0',nMax的長度包含有'\0'字符,實際的分析如下:
- 如果nMax <= 字符數,讀取(nMax-1)個字符+0x00;
- 如果nMax = 字符數 + 1,讀取nMax個字符+0x00;
- 如果nMax > 字符數,讀取nMax個字符+0x0A('\n') + 0x00;
- 如果文件有多行,則當文件沒有讀完時,返回NOT NULL,讀到文件尾,返回NULL。
- BOOL ReadString(CString& rString);
- throw(CFileException);
讀取一行文本到rString中,遇到回車換行符停止讀取,回車和換行符均不讀到rString中,尾部也不添加'0x00'。如果文件有多行,則當文件沒有讀完時,返回TRUE,讀到文件尾,返回FALSE。
寫函數
- virtual void WriteString(LPTSTR lpsz);
- throw(CFileException);
將緩衝區中的數據寫入到與CStdioFile對象相關聯的文件中,不支持CString類型數據寫入,結束的'\0'不被寫入到文件中,lpsz緩衝區中的所有換行符被替換爲回車換行符,即'\n'轉換爲'\r\n'。
三、CStdioFile類編程原理
假如要進行的文件操作只是簡單的讀寫整行的字符串,那麼最好使用CStdioFile。首先把文本文件的每行數據讀到一個緩衝區,然後使用sscanf把它轉化爲字符格式。
例如在一個txt文件裏每一行數據格式是這樣的:
A1 A2 A3 A3 ......An
那麼讀取的主體代碼是:
- CStdioFile File; // 定義一個CStdioFile類變量File
- CString FileData; // 定義一個CString,作爲一個緩衝區
- //定義n個臨時字符串變量,大小依據實際情況,這裏暫設爲10
- char TempStr1[10],TempStr2[10]......TempStrN[10];
- File.ReadString(FileData); // 將一行數據讀到緩衝區
- //將該行數據的n個字符讀到n個臨時字符串變量
- sscanf(FileData,"%s %s %s %s ......%s",TempStr1,TempStr2......TempStrN);
這種讀法的一個好處是對文本格式要求不嚴,如下面的格式也可以
(前面可有未知個空格) A1 A2 (兩個數據之間也可有未知個空格) A3 A3 ......An
四、CStdioFile類編程應用
以一個單文檔程序爲例。該程序的主要功能是讀取文本文件的座標數據,然後在客戶區裏用直線將這些座標連起來,並顯示。
1、 啓動Visual C++6.0,生成一個單文檔的工程,將該工程命名爲ReadCoodinate。
2、 添加一個“讀取文本數據”的菜單項。
3、 給視圖類添加兩個public變量:
- CArray<CPoint,CPoint> m_PointArray; // 用於記錄座標點數據
- int m_PointNum; // 用於記錄座標點個數,在視圖類構造函數中初始化爲0。
4、 給“讀取文本數據”添加相應的單擊消息響應函數。代碼如下:
- void CReadCoodinateView::OnReaddata()
- {
- // TODO: Add your command handler code here
- CFileDialog dlg(TRUE); // 定義一個文件對話框變量
- if(dlg.DoModal()==IDOK)
- {
- CString m_FilePath = dlg.GetPathName(); //取得文件路徑及文件名
- CStdioFile File;
- File.Open(m_FilePath,CFile::modeRead); //以讀模式打開文本文件
- CString FileData; //定義一個CString變量作爲緩衝區
- //定義兩個臨時字符串,並初始化爲'\0'
- char TempStr1[10];
- char TempStr2[10];
- memset(TempStr1,'\0',10);
- memset(TempStr2,'\0',10);
- File.ReadString(FileData);//讀取第一行數據,第一行數據爲座標點數據
- sscanf(FileData,"%s",TempStr1);
- m_PointNum = atoi(TempStr1); // 獲取座標點個數
- //逐行讀取座標數據
- for (int i = 0;i<m_PointNum;i++)
- {
- File.ReadString(FileData);
- sscanf(FileData,"%s %s",TempStr1,TempStr2);
- m_PointArray.Add(CPoint(atoi(TempStr1),atoi(TempStr2)));//將其存入座標點數組
- }
- CDC *pDC = GetDC(); //獲取設備環境;
- //根據座標點繪出直線
- for (i = 0;i<m_PointNum-1;i++)
- {
- pDC->MoveTo(m_PointArray[i].x, m_PointArray[i].y);
- pDC->LineTo(m_PointArray[i+1].x, m_PointArray[i+1].y);
- }
- ReleaseDC(pDC); //使用完後,釋放設備環境
- }
- }
其中示例數據文件的格式是這樣的:(第一行爲座標個數,餘下的是座標點數據)
5
10 20
30 40
45 85
100 120
200 300
這個程序的一個優點是對文本數據格式並不嚴格,只要x座標和y座標中間有一個空格就可以了。若要將文本數據傳化爲其它形式的數據(如將字符型數據轉化爲浮點型可以利用函數atof()),其中原理是一樣的。