一、C++文件類及用法
C++ 標准庫提供了 3 個類用於實現文件操作,它們統稱為文件流類,這 3 個類分別為:
- ifstream:專用於從文件讀取數據
- ofstream:專用於向文件寫入數據
- fstream:可讀可寫
這三個文件流類都位於 fstream 頭文件中

- fstream 類擁有 istream、ostream 類的全部成員方法。
- fstream 頭文件中並沒有定義可直接使用的 fstream、ifstream 和 ofstream 類對象
fstream 類常用成員方法
| 成員方法名 | 適用類對象 | 功能 |
|---|---|---|
| open() | fstream | 打開指定文件,使其與文件流對象關聯 |
| is_open() | ifstream | 檢查指定文件是否已打開。 |
| close() | ofstream | 關閉文件,切斷和文件流對象的關聯。 |
| swap() | ofstream | 交換 2 個文件流對象。 |
| operator>> | (i)fstream | 重載 >> 運算符,用於從指定文件中讀取數據。 |
| gcount() | (i)fstream | 返回上次從文件流提取出的字符個數。該函數常和 get()、getline()、ignore()、 peek()、read()、readsome()、putback() 和 unget() 聯用。 |
| get() | (i)fstream | 從文件流中讀取一個字符,同時該字符會從輸入流中消失。 |
| getline(str,n,ch) | (i)fstream | 從文件流中接收 n-1 個字符給 str 變量,當遇到指定 ch 字符時會停止讀取,默 認情況下 ch 為 '\0'。 |
| ignore(n,ch) | (i)fstream | 從文件流中逐個提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 個 字符,或者當前讀取的字符為 ch。 |
| peek() | (i)fstream | 返回文件流中的第一個字符,但並不是提取該字符。 |
| putback(c) | (i)fstream | 將字符 c 置入文件流(緩沖區)。 |
| operator<< | (o)fstream | 重載 << 運算符,用於向文件中寫入指定數據。 |
| put() | (o)fstream | 向指定文件流中寫入單個字符。 |
| write() | (o)fstream | 向指定文件中寫入字符串。 |
| tellp() | (o)fstream | 用於獲取當前文件輸出流指針的位置。 |
| seekp() | (o)fstream | 設置輸出文件輸出流指針的位置。 |
| flush() | (o)fstream | 刷新文件輸出流緩沖區。 |
| good() | (i/o)fstream | 操作成功,沒有發生任何錯誤。 |
| eof() | (i/o)fstream | 到達輸入末尾或文件尾。 |
const string url = "asdfasdfasasdf";
//創建一個 fstream 類對象
fstream fs;
//將 test.txt 文件和 fs 文件流關聯
fs.open("test.txt", ios::out);
//向 test.txt 文件中寫入 url 字符串
fs.write(url,30);
fs.close();
二、open開打模式
打開文件目的:
- 通過指定文件名,建立起文件和文件流對象的關聯
- 指明文件的使用方式。使用方式有只讀、只寫、既讀又寫、在文件末尾添加數據、以文本方式使用、以二進制 方式使用等多種。
打開文件可以通過兩種方式:
- 調用流對象的 open 成員函數打開文件。
- 定義文件流對象時,通過構造函數打開文件。
void open(const char* szFileName, int mode)
- 第一個參數:指向文件名的指針
- 第二個參數:打開模式
文件打開模式
| 模式標記 | 適用對象 | 作用 |
|---|---|---|
| ios::in | (i)fstream | 打開文件用於讀取數據。如果文件不存在,則打開出錯。 |
| ios::out | (o)fstream | 打開文件用於寫入數據。如果文件不存在,則新建該文件;如果文件原來就存 在,則打開時清除原來的內容。 |
| ios::app | (o)fstream | 打開文件,用於在其尾部添加數據。如果文件不存在,則新建該文件。 |
| ios::ate | (i)fstream | 打開一個已有的文件,並將文件讀指針指向文件末尾(讀寫指 的概念后面解 釋)。如果文件不存在,則打開出錯。 |
| ios::trunc | (o)fstream | 打開文件時會清空內部存儲的所有數據,單獨使用時與 ios::out 相同。 |
| ios::binary | (i/o)fstream | 以二進制方式打開文件。若不指定此模式,則以文本模式打開。 |
| ios::in | ios::out | fstream | 打開已存在的文件,既可讀取其內容,也可向其寫入數據。文件剛打開時,原有內容保持不變。如果文件不存在,則打開出錯。 |
| ios::in | ios::out | ofstream | 打開已存在的文件,可以向其寫入數據。文件剛打開時,原有內容保持不變。如果文件不存在,則打開出錯。 |
| ios::in | ios::out | ios::trunc | fstream | 打開文件,既可讀取其內容,也可向其寫入數據。如果文件本來就存在,則打 開時清除原來的內容;如果文件不存在,則新建該文件。 |
ios::binary 可以和其他模式標記組合使用,如 ios::in| ios::binary。
定義流對象時,在構造函數中給出文件名和打開模式也可以打開文件。以 ifstream 類為例,它有如下構造函數:
ifstream::ifstream (const char* szFileName, int mode = ios::in, int);
- szFileName:文件名指針
- mode:打開文件模式,默認 ios::in
- 極少使用
ifstream inFile("c:\\tmp\\test.txt", ios::in);
if (inFile)
inFile.close();
else
cout << "test.txt doesn't exist" << endl;
ofstream oFile("test1.txt", ios::out);
if (!oFile)
cout << "error 1";
else
oFile.close();
fstream oFile2("tmp\\test2.txt", ios::out | ios::in);
if (!oFile2)
cout << "error 2";
else
oFile.close();
三、文本打開方式和二進制打開方式
文本文件通常用來保存肉眼可見的字符,比如 .txt 文件、.c 文件、.dat 文件等,文本文件中通常采用 ASCII、UTF-8、GBK 等字符編碼,文本編輯器可以識別出這些編碼格式,並將編碼值轉換成字符展示出來。
二進制文件通常用來保存視頻、圖片、程序等不可閱讀的內容,用文本編輯器打開這些文件,會看到一堆亂碼。
文本方式和二進制方式並沒有本質上的區別,只是對於換行符的處理不同。
- 在 UNIX/Linux 平台中,用文本方式或二進制方式打開文件沒有任何區別,因為文本文件以 \n(ASCII 碼為 0x0a)作為換行符號。
- Windows 平台上,文本文件以連在一起的 \r\n 作為換行符號。如果以文本方式打開文件,當讀取文件時, 程序會將文件中所有的 \r\n 轉換成一個字符 \n。
- 同樣當寫入文件時,程序會將 \n 轉換成 \r\n 寫入。
總的來說,Linux 平台使用哪種打開方式都行;Windows 平台上最好用 "ios::in | ios::out" 等打開文本文件, 用 "ios::binary" 打開二進制文件。但無論哪種平台,用二進制方式打開文件總是最保險的。
四、close() 文件方法
調用 close() 方法關閉已打開的文件,可以理解為是切斷文件流對象和文件之間的關聯。注意,close() 方法的功能僅是切斷文件流與文件之間的關聯,該文件流並會被銷毀,其后續還可用於關聯其它的文件。
close() 方法語法格式:
void close();
實際上,當文件流對象的生命周期結束時,會自行調用其析構函數,該函數內部在銷毀對象之前,會先調用 close() 方法切斷它與任何文件的關聯,最后才銷毀它。但是一定要手動調用 close() 方法關閉,避免出現未知錯誤,如讀寫文件失敗。
如果不想頻繁地打開/關閉文件,可以使用 flush() 方法及時刷新輸出流緩沖區,也能起到防止寫入文件失敗的作用。
C++ 中使用 open() 打開的文件,在讀寫操作執行完畢后,應及時調用 close() 方法關閉文件,或者對 文件執行寫操作后及時調用 flush() 方法刷新輸出流緩沖區。
五、文本文件讀寫操作詳解
- 文件中存儲的數據並沒有類型上的分別,統統都是字符。所謂以文本形式讀/寫文件,就是直白地 將文件中存儲的字符(或字符串)讀取出來,以及將目標字符(或字符串)存儲在文件中。
- 以二進制形式讀/寫文件,操作的對象不再是打開文件就能看到的字符,而是文件底層存儲的二進制數據。 更詳細地講,當以該形式讀取文件時,讀取的是該文件底層存儲的二進制數據;同樣,當將某數據以二進制形式寫入到文件中時,寫入的也是其對應的二進制數據。
C++ 標准庫中,提供了 2 套讀寫文件的方法組合,分別是:
- 使用 >> 和 << 讀寫文件:適用於以文本形式讀寫文件;
- 使用 read() 和 write() 成員方法讀寫文件:適用於以二進制形式讀寫文件。
int x,sum=0;
ifstream srcFile("in.txt", ios::in); //以文本模式打開 in.txt 備讀
// 假設 in.txt 中為 "10 20 30 40 50" 字符串
if(!srcFile){ // 打開失敗
cout << "error opening source file." << endl;
return 0;
}
ofstream destFile("out.txt", ios::out); //以文本模式打開 out.txt 備寫
if (!destFile) {
srcFile.close(); //程序結束前不能忘記關閉以前打開過的文件
cout << "error opening destination file." << endl;
return 0;
}
//可以像用 cin 那樣用 ifstream 對象
while (srcFile >> x) { //將"10 20 30 40 50" 字符串解析成 int 整數
sum += x;
//可以像 cout 那樣使用 ofstream 對象 destFile << x << " ";
destFile << x << " "; // destFile 最終會寫入 "10 20 30 40 50" 字符串
}
cout << "sum:" << sum << endl; // 150
destFile.close();
srcFile.close();
六、read()和write()讀寫二進制文件
對於類對象,如果以文本形式寫入,每個對象的信息所占的字節數將不同,不利於查找指定學生信息,查找效率低下,這種情況應該以二進制形式寫入對象。
read() 方法用於以二進制形式從文件中讀取數據;write() 方法用於以二進 制形式將數據寫入文件。
write
ofstream 和 fstream 的 write() 成員方法實際上繼承自 ostream 類,其功能是將內存中 buffer 指向的 count 個字節的內容寫入文件,基本格式如下:
ostream & write(char* buffer, int count);
- buffer:指定要寫入文件的二進制數據的起始位置
- count:指定寫入字節的個數
該方法可以被 ostream 類的 cout 對象調用,常用於向屏幕上輸出字符串。同時,它還可以被 ofstream 或者 fstream 對象調用,用於將指定個數的二進制數據寫入文件。
write() 方法會從文件寫指針指向的位置將二進制數據寫入。文件剛打開時,文件寫指針指向的是文件的開頭(如果以 ios::app 方式打開,則指向文件末尾),用 write() 方法寫入 n 個字節,寫指針指向的位置就向后移動 n 個字節。
CStudent s;
ofstream outFile("students.dat", ios::out | ios::binary);
while (cin >> s.szName >> s.age)
outFile.write((char*)&s, sizeof(s));
outFile.close();
read
ifstream 和 fstream 的 read() 方法實際上繼承自 istream 類,其功能正好和 write() 方法相反,即從文件中讀 取 count 個字節的數據。該方法的語法格式如下:
istream & read(char* buffer, int count);
- buffer:指定讀取字節的起始位置
- count:指定讀取字節的個數
read() 方法從文件讀指針指向的位置開始讀取若干字節。
CStudent s;
ifstream inFile("students.dat",ios::in|ios::binary); //二進制讀方式打開 if(!inFile) {
cout << "error" <<endl;
return 0;
}
while(inFile.read((char *)&s, sizeof(s))) { //一直讀到文件結束
cout << s.szName << " " << s.age << endl;
}
inFile.close();
七、get()和 put() 逐個讀寫
逐個讀取文件中存儲的字符,或者逐個將字符存儲到文件中的情形,可以使用 get() 和 put() 成員方法實現。
當 fstream 和 ofstream 文件流對象調用 put() 方法時,該方法的功能就變成了向指定文件中寫入單個字符。 put() 方法的語法格式如下:
ostream& put (char c);
- c:指定要寫入文件的字符
char c;
// 以二進制形式打開文件
ofstream outFile("out.txt", ios::out | ios::binary);
if (!outFile) {
cout << "error" << endl;
return 0;
}
while (cin >> c) {
//將字符 c 寫入 out.txt 文件
outFile.put(c);
}
outFile.close();
注意,由於文件存放在硬盤中,硬盤的訪問速度遠遠低於內存。如果每次寫一個字節都要訪問硬盤,那么文件 的讀寫速度就會慢得不可忍受。因此,操作系統在接收到 put() 方法寫文件的請求時,會先將指定字符存儲在 一塊指定的內存空間中(稱為文件流輸出緩沖區),等刷新該緩沖區(緩沖區滿、關閉文件、手動調用 flush()方法等,都會導致緩沖區刷新)時,才會將緩沖區中存儲的所有字符“一股腦兒”全寫入文件。
get()方法的語法格式有很多,這里僅介紹常見的2中:
int get(); // 返回讀取到的字符的 ASCII碼,末尾返回EOF
istream& get (char& c); // 將讀到字符賦值給 c
八、getline() 讀取一行字符串
getline() 方法定義在 istream 類中,而 fstream 和 ifstream 類繼承自 istream 類,因此 fstream 和 ifstream 的類對象可以調用 getline() 成員方法。
當文件流對象調用 getline() 方法時,該方法的功能就變成了從指定文件中讀取一行字符串。該方法有以下 2 種 語法格式:
istream & getline(char* buf, int bufSize); // 從文件輸入流緩沖區中讀取 bufSize-1 個字符到 buf,或遇到 \n 為止
istream & getline(char* buf, int bufSize, char delim); // 從文件輸入流緩沖區中讀取 bufSize-1 個字符到 buf,或讀到 delim 字符為止
如果文件輸入流中 \n 或 delim 之前的字符個數達到或超過 bufSize,就會導致讀取失敗。
char c[40];
//以二進制模式打開 in.txt 文件
ifstream inFile("in.txt", ios::in | ios::binary);
//判斷文件是否正常打開
if (!inFile) {
cout << "error" << endl;
return 0;
}
//從 in.txt 文件中讀取一行字符串,最多不超過 39 個
inFile.getline(c, 40);
cout << c ;
inFile.close();
九、移動和獲取文件讀寫指針(seekp、seekg、tellg、tellp)
- ifstream 類和 fstream 類有 seekg 成員函數,可以設置文件讀指針的位置;
- ofstream 類和 fstream 類有 seekp 成員函數,可以設置文件寫指針的位置。
位置指距離文件開頭多少個字節。
這兩個函數的原型如下:
ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
mode 代表文件讀寫指針的設置模式,有以下三種選項:
- ios::beg:讓文件讀指針(或寫指針)指向從文件開始向后的 offset 字節處。offset 等於 0 即代表文件開頭。 在此情況下,offset 只能是非負數。
- ios::cur:在此情況下,offset 為負數則表示將讀指針(或寫指針)從當前位置朝文件開頭方向移動 offset 字節, 為正數則表示將讀指針(或寫指針)從當前位置朝文件尾部移動 offset 字節,為 0 則不移動。
- ios::end:讓文件讀指針(或寫指針)指向從文件結尾往前的 |offset|(offset 的絕對值)字節處。在此情況下,offset 只能是 0 或者負數。
- ifstream 類和 fstream 類還有 tellg 成員函數,能夠返回文件讀指針的位置;
- ofstream 類和 fstream 類還有 tellp 成員函數,能夠返回文件寫指針的位置。
這兩個成員函數的原型如下:
int tellg(); // 返回文件讀指針的位置
int tellp(); // 返回文件寫指針的位置
class CStudent{
public:
char szName[20];
int age;
};
int main(){
CStudent s;
fstream ioFile("students.dat", ios::in|ios::out); //用既讀又寫的方式打開
if(!ioFile) {
cout << "error" ;
return 0;
}
ioFile.seekg(0,ios::end); //定位讀指針到文件尾部,以便用以后 tellg 獲取文件長度
int L = 0,R; // L 是折半查找范圍內第一個記錄的序號,R 是折半查找范圍內最后一個記錄的序號
R = ioFile.tellg() / sizeof(CStudent) - 1;
//首次查找范圍的最后一個記錄的序號就是: 記錄總數- 1
do {
int mid = (L + R)/2; //要用查找范圍正中的記錄和待查找的名字比對
ioFile.seekg(mid *sizeof(CStudent),ios::beg); //定位到正中的記錄
ioFile.read((char *)&s, sizeof(s));
int tmp = strcmp( s.szName,"Jack");
if( tmp == 0 ) { //找到了
s.age = 20;
ioFile.seekp(mid*sizeof(CStudent),ios::beg);
ioFile.write((char*)&s, sizeof(s));
break;
}
else if (tmp > 0) //繼續到前一半查找
R = mid - 1 ;
else //繼續到后一半查找 }
L = mid + 1;
}while(L <= R);
ioFile.close();
return 0;
}
