文件流類與文件流對象
- C++ 中對文件的操作是由文件流類完成的
- 通過文件流類可以將文件和流聯繫起來
- 文件流類分爲輸入流,輸出流和輸入/輸出流
- 再對文件進行操作之前,需要將文件流說明爲 ifstream,ofstream 或者 fstream 的對象
文件操作過程
在 C 語言中,對文件的操作步驟爲:
- 打開文件
- 進行讀寫
- 關閉文件
而在 C++ 中,對文件的操作步驟爲:
- 定義文件流對象
- 打開文件
- 進行讀寫
- 關閉文件
定義流對象
ifstream ifile;
ofstream ofile;
fstream f;
只是在定義流對象之前之前需要包含對應的頭文件。
打開文件
void open(const std::string& __s, ios_base::openmode __mode = ios_base::in)
void open(const std::string& __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc)
void open(const std::string& __s, ios_base::openmode __mode = ios_base::in | ios_base::out)
打開文件的函數爲 open(),第一個參數表示要打開的文件名,第二個參數表示文件的打開方式,可以看出三種不同的文件流對象,具有不同的默認打開方式。
主要的文件打開方式爲:
文件打開方式 | 值 | 描述 |
ios::in | 0x01 | 以讀方式打開文件,若文件不存在則報錯 |
ios::out | 0x02 | 以寫方式打開文件,若文件不存在則創建 |
ios::app | 0x08 | 在文件末尾添加內容,如果文件不存在,則報錯 |
ios::trunc | 0x10 | 若文件存在,則清除文件所有內容,若文件不存在,則創建新文件 |
ios::binary | 0x80 | 以二進制方式打開文件,缺省時以文本方式打開文件 |
ios::nocreate | 0x20 | 打開一個已有文件,如果文件不存在,則打開失敗 |
ios::noreplace | 0x40 | 如果要打開的文件已經存在,則打開失敗 |
- 通常很少單獨使用某一種打開方式,一般會將幾種打開方式進行組合:
ios::in | ios::out | 以讀寫方式打開文件,ifstream 對象默認的打開方式 |
ios::in | ios::binary | 以二進制讀方式打開文件 |
ios::out | ios::binary | 以二進制寫方式打開文件 |
ios::in | ios::out | ios::binary | 以二進制讀寫方式打開文件 |
ios_base::out | ios_base::trunc | ofstream 對象默認的打開方式 |
- C++ 中文件都是藉助文件流類打開的,而類中存在構造函數,因此也可以直接利用構造函數打開
- C 中是通過判斷指針是否爲空來判斷是否正確地打開了文件,而 C++ 中則是通過類對象來進行判斷的
關閉文件
文件操作之後,應使用 close() 來關閉文件。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream ifile("test.txt");
ofstream ofile("out.txt");
char buf[100];
if(!ifile && !ofile)
cout<<"error"<<endl;
while(ifile)
{
ifile>>buf;
ofile<<" "<<buf;
}
ifile.close();
ofile.close();
return 0;
}
使用上邊的程序可以將 test.txt 中的內容對應的拷貝到 out.txt 文件中,只是 out.txt 中沒有換行。
流文件狀態與判斷
標識位
在 ios_base.h 中,給出了一個判斷流文件狀態的標誌位:
enum _Ios_Iostate
{
_S_goodbit = 0,
_S_badbit = 1L << 0,
_S_eofbit = 1L << 1,
_S_failbit = 1L << 2,
_S_ios_iostate_end = 1L << 16,
_S_ios_iostate_max = __INT_MAX__,
_S_ios_iostate_min = ~__INT_MAX__
};
/// Indicates a loss of integrity in an input or output sequence (such
/// as an irrecoverable read error from a file).
static const iostate badbit = _S_badbit;
/// Indicates that an input operation reached the end of an input sequence.
static const iostate eofbit = _S_eofbit;
/// Indicates that an input operation failed to read the expected
/// characters, or that an output operation failed to generate the
/// desired characters.
static const iostate failbit = _S_failbit;
/// Indicates all is well.
static const iostate goodbit = _S_goodbit;
上邊的頭文件中以枚舉的形式給出了幾個標誌位,這幾個標誌位各自代表有各自的含義。
函數
eof()
如果讀取文件到達文件末尾,返回 true
bad()
如果讀寫過程中出錯,返回 true
fail()
如果讀寫過程中出錯或者格式錯誤,返回 true
good()
如果發生了之前的任何情況,該函數都返回 false
clear()
標誌位被置位後,可以使用 clear() 來重置標誌位
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
cout<<cin.eof()<<" "<<cin.bad()<<" "<<cin.fail()<<" "<<cin.good()<<endl;
cin.clear();
int i;
cin>>i;
cout<<cin.eof()<<" "<<cin.bad()<<" "<<cin.fail()<<" "<<cin.good()<<endl;
cin.clear();
return 0;
}
結果爲:
0 0 0 1
abc
0 0 1 0
!cin
在打開文件的時候,我們使用了 !cin 的形式來判斷文件是否打開。我們知道 cin 其實是一個流對象,那麼我們又爲什麼能夠使用流對象進行邏輯判斷呢?
這隻能說明在 C++ 中爲流對象提供了某種轉換函數,使之可以將一個流對象轉換成爲可以進行邏輯操作的類型。
在 basic_ios.h 文件中存在兩個重載函數,它們爲這種行爲做出瞭解釋:
operator void*() const
{ return this->fail() ? 0 : const_cast<basic_ios*>(this); }
// 函數會在 while(cin) 或者 if(cin) 時被調用,會將流對象轉化爲 void* 類型
// 重載 void * 的可以實現連等式
// 看着像是運算符重載,但是 C++ 內部並沒有 void * 這個運算符
bool operator!() const
{ return this->fail(); }
// 函數會在 while(!cin) 或者 if(!cin) 時被調用,會將流對象轉化爲 bool 類型
// 這個是對 ! 的重載
可以對自定義類進行上述兩個函數的重載,測試結果:
#include <iostream>
#include <fstream>
using namespace std;
class TEST
{
public:
operator void *() const
{
cout<<"operator void *() const"<<endl;
return (void *)(this);
}
bool operator !() const
{
cout<<"bool operator !() const"<<endl;
return true;
}
};
int main()
{
TEST t;
if(t)
cout<<"yes"<<endl;
else
cout<<"no"<<endl;
if(!t)
cout<<"yes"<<endl;
else
cout<<"no"<<endl;
return 0;
}
結果爲:
operator void *() const
yes
bool operator !() const
yes
不得不說,C++ 真的很神奇。
文件讀寫
就像 cout 和 cin 一樣,如果要讀取數據,可以使用文件流類的 get,getline 和 >> 進行操作;如果要寫入數據,可以使用 put,write 和 << 進行操作。
流狀態的查詢和控制
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream ifile("test.txt");
ofstream ofile("out.txt");
char buf[100];
if(!ifile && !ofile)
cout<<"error"<<endl;
while(ifile >> buf,!ifile.eof())
{
ofile<<" "<<buf;
if(ifile.bad())
throw runtime_error("IO stream corrupted");
if(ifile.fail())
{
cerr<<"bad data";
ifile.clear(istream::failbit);
continue;
}
}
ifile.close();
ofile.close();
return 0;
}
- 上面的循環會不斷迭代,直到到達文件結束符或者發生不可恢復的讀取錯誤爲止。
- 循環條件使用了逗號操作符,因此會返回最右邊操作數作爲整個表達式的結果。
- 如果 ifile 到達文件結束符,條件則爲假,退出循環,如果 ifile 沒有到達文件結束符,則都會進入循環
- 在循環中,首先檢查流是否已破壞,如果流已經被破壞,則拋出異常,退出循環
- 如果輸入無效,則輸出警告並清除 failbit 狀態,continue 下一次循環
文件讀寫實例
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream ifile("test.txt");
ofstream ofile("out.txt");
char buf[1024];
if(!ifile && !ofile)
cout<<"error"<<endl;
while(ifile.getline(buf,1024,'\n'))
{
ofile<<buf<<" "<<endl;
if(ifile.eof())
break;
}
ifile.close();
ofile.close();
return 0;
}
隨機讀寫函數
成員函數 | 描述 |
tellg() | 返回當前指針位置(輸入流操作) |
seekg() | 絕對移動 |
seekg() | 相對操作 |
seekp() | 絕對移動(輸出流操作) |
seekp() | 相對操作 |
tellp() | 返回當前指針位置 |
g 表示 get,爲輸入,p 表示 put,爲輸出。參照位置爲:
成員 | 意義 |
ios::beg = 0 | 相對於文件頭 |
ios::cur = 1 | 相對於當前位置 |
ios::end = 2 | 相對於文件尾 |