【C++ 筆記】檔案處理 - part 27
===
目錄(Table of Contents):
[TOC]
---
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
至此之前,我們已經有學過 `<iostream>` 的標準輸入輸出流 cin, cout。
那本篇要學的就是 `<fstream>` 裡面的三種流,`ofstream`, `ifstream`, `fstream`。
基本檔案處理(File Handing)
---
`<fstream>` 定義了三種資料型態:
表格參考:[菜鳥教程](https://www.runoob.com/cplusplus/cpp-files-streams.html)
| 資料型態 | 敘述 |
| -------- | -------- |
| ofstream | (o:Output)輸出檔案流,用於建立檔案並向檔案寫入訊息。 |
| ifstream | (i:Input)輸入檔案流,用於從檔案中讀取訊息。|
| fstream | 上述兩種資料型態的結合體,亦即同時具備兩種資料型態的功能。 |
要做到檔案處理,需引入 `<iostream>` 跟 `<fstream>`。
### 檔案處理的基本操作
主要分成:
- 開啟檔案
- 讀寫操作
- 關閉檔案
### 開啟檔案
語法:
```cpp
fstream str("filename.ext", mode);
```
- str:給這個流的名字。
- filename:檔名。
- mode:表示要與這個檔案互動的方式。
### 檔案開啟模式(mode)
Table Source:https://www.geeksforgeeks.org/cpp/file-handling-c-classes/
| 模式(Mode) | 說明(Description) |
| ------------- | --------------------------- |
| `ios::in` | 以讀取模式開啟檔案。若檔案不存在,將無法開啟。 |
| `ios::out` | 以寫入模式開啟檔案:內部的串流緩衝區支援輸出操作。 |
| `ios::binary` | 以二進位模式進行操作,而非文字模式。 |
| `ios::ate` | 輸出的起始位置會設在檔案結尾。 |
| `ios::app` | 所有輸出操作都會發生在檔案末端,並附加於現有內容之後。 |
| `ios::trunc` | 開啟檔案時會清除其原有內容(若存在)。 |
這些模式可透過 OR 運算子 `|` 同時做到兩種操作:
`fstream str("file.txt", ios::in | ios::out)`
---
`ios::in` 範例:
將 example.txt 與 eg.cpp 放到同目錄下。


```cpp=
// eg.cpp
#include <iostream>
#include <fstream>
using namespace std;
int main(){
fstream file("example.txt", ios::in);
if (file.is_open()){ // is_open() 回傳 bool 檢查檔案是否成功開啟
string line;
getline(file, line);
cout << "example.txt 的內容是:" << line << endl;
file.close(); // 關閉檔案
}
else{
cout << "檔案可能不存在" << endl;
}
return 0;
}
```
使用 Code::Blocks 的輸出結果為:

---
`ios::out` 寫入檔案(會清空原本的內容):
原本 example.txt 寫的是 `Big Handsome Boy`,透過以下程式碼後,會覆蓋掉原本的字。
```cpp=
#include <iostream>
#include <fstream>
using namespace std;
int main() {
fstream file("example.txt", ios::out);
file << "這是新資料。" << endl;
file.close();
return 0;
}
```
執行後:

---
`ios::ate` 開啟檔案後會跳到檔案尾:
```cpp=
#include <iostream>
#include <fstream>
using namespace std;
int main() {
fstream file("example.txt", ios::in | ios::out | ios::ate);
if (file.is_open()) {
file << "附加內容(位置不強制)" << endl;
file.close();
}
return 0;
}
```
此時檔案的寫入就會從檔案結尾開始:

---
`ios::app` 強制所有寫入附加到檔案結尾:
```cpp=
#include <iostream>
#include <fstream>
using namespace std;
int main() {
fstream file("example.txt", ios::app);
file << "這行會附加在檔案末尾。" << endl;
file.close();
return 0;
}
```
執行後:

---
`ios::trunc` 清空檔案:
`ios::out` 本身預設就帶有 `ios::trunc`,若加上 `ios::app` 則不會清空。
```cpp=
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream file("example.txt", ios::out | ios::trunc);
file << "檔案原有內容被清除。" << endl;
file.close();
return 0;
}
```
### 讀取檔案
1. 透過自定義的 fstream 名稱加上 `>>` 運算子可讀取檔案內的內容給變數。
example.txt:

```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
fstream file("example.txt");
string s;
file >> s;
cout << "讀取到的是 : " << s;
return 0;
}
```
Output:

這種方式就如同用 cin 一樣,一旦讀到含有空格的字串,那就只會讀到第一個字串。
2. 用 `getline(file, s)` 去讀。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
fstream file("example.txt");
string s;
getline(file, s);
cout << "讀取到的是 : " << s;
return 0;
}
```
Output:

### 關閉檔案
當檔案處理完畢後,很重要的一件事是:務必要關上檔案。
語法:
`file.close()`
為什麼呢?除了長時間開啟檔案,一直在那占用資源以外,關閉檔案也能以避免記憶體洩漏、資料遺失等問題。
檔案處理上的錯誤
---
為什麼要知道這些錯誤?主要是防範於未然,有時候像是忘記要讀取,那這時候可以加上一個自定義的讀取錯誤來提醒自己要加上讀取。
### 檔案開啟錯誤
用 `is_open()` 函式去判斷是否能開啟檔案。
如下範例(Source from [GeeksForGeeks](https://www.geeksforgeeks.org/cpp/file-handling-c-classes/)):
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
fstream file("nonexistent_file.txt", ios::in);
// Check if the file is opened
if (!file.is_open()) {
cerr << "Error: Unable to open file!" << endl;
return 1;
}
file.close();
return 0;
}
```
如果目錄下沒有 noneexistent_file.txt 文件的話,就會直接輸出這行:
```
Error: Unable to open file!
```
註:cerr 被稱為標準錯誤輸出,與原本的 cout 相比,他沒有緩衝就直接輸出了,如其名,主要用於輸出錯誤、警告等訊息。
### 讀寫失敗
`if (!getline(file, s))` 判斷是否有資料讀取。
稍微改一下的範例(Source from [GeeksForGeeks](https://www.geeksforgeeks.org/cpp/file-handling-c-classes/)):
如下程式碼中,只有給 `ios::out`,而沒有 `ios::in`,也不做任何讀取的動作,那當然就會出錯囉。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
fstream file("example.txt", ios::out);
if (!file.is_open()) {
cerr << "Error: Unable to open file!" << endl;
return 1;
}
string s;
if (!getline(file, s)){
cerr << "Error: Failed to read data." << endl;
}
file.close();
return 0;
}
```
### 檔案結尾(EOF)錯誤
臭名昭著的 EOF,全名為 End of file,競程上時常碰到的對手。
可用 `eof()` 函式判斷是否有到 EOF。
範例(Source from [GeeksForGeeks](https://www.geeksforgeeks.org/cpp/file-handling-c-classes/)):
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
ifstream file("GFG.txt");
if (!file.is_open()) {
cerr << "Error: Unable to open file!" << endl;
return 1;
}
string line;
while (getline(file, line))
cout << line << endl;
// Check for eof
if (file.eof())
cout << "Reached end of file." << endl;
else
cerr << "Error: File reading failed!" << endl;
file.close();
return 0;
}
```
總結
---
### 基本檔案處理(File Handling)
需包含標頭檔 `<iostream>` 和 `<fstream>`。
`<fstream>` 定義三種資料型態:
- ofstream:輸出檔案流,用於建立檔案並寫入資料。
- ifstream:輸入檔案流,用於從檔案讀取資料。
- fstream:結合輸入與輸出功能的檔案流。
### 檔案開啟與模式
開啟檔案語法:
```cpp
fstream file("filename.ext", mode);
```
常用模式(可用 | 組合):
* ios::in:讀取模式,檔案不存在則開啟失敗。
* ios::out:寫入模式,會清空檔案內容(除非搭配 ios::app)。
* ios::binary:二進位模式。
* ios::ate:開啟後指標移到檔案尾,可從尾端開始寫入。
* ios::app:所有寫入附加在檔案末尾。
* ios::trunc:開啟時清空檔案內容(ios::out 預設帶此模式)。
### 讀寫操作
- 讀取:
1. 使用 >> 運算子讀取資料(遇空白停止)。
2. 使用 `getline(file, string_var)` 讀取整行文字。
- 寫入:
- 使用 << 運算子寫入資料。
### 關閉檔案
使用 `file.close()` 關閉檔案,釋放資源,避免資料遺失或記憶體洩漏。
### 錯誤處理
- 開啟檔案失敗可用 `is_open()` 判斷並提示錯誤。
- 讀取失敗可用 `if (!getline(file, s))` 判斷。
- 使用 `eof()` 判斷是否到達檔案結尾,避免讀取過頭。
參考資料
---
[C++中cout和cerr的区别?_cerr<<-CSDN博客](https://blog.csdn.net/Garfield2005/article/details/7639833)
[C++ cerr object | W3Schools](https://www.w3schools.com/cpp/ref_iostream_cerr.asp)
[File Handling through C++ Classes - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/file-handling-c-classes/)
[C++ 文件和流 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-files-streams.html)