---
# System prepended metadata

title: 【C++ 筆記】檔案處理 - part 27
tags: [程式設計, C++, 程式語言]

---

【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 放到同目錄下。

![image](https://hackmd.io/_uploads/BksSEfaEex.png)

![image](https://hackmd.io/_uploads/SkZINM6Ell.png)

```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 的輸出結果為：

![image](https://hackmd.io/_uploads/SkROEMTNgx.png)

---

`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;
}
```

執行後：

![image](https://hackmd.io/_uploads/B1rJrG6Nel.png)

---

`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;
}
```

此時檔案的寫入就會從檔案結尾開始：

![image](https://hackmd.io/_uploads/HyeRQX64ge.png)

---

`ios::app` 強制所有寫入附加到檔案結尾：

```cpp=
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    fstream file("example.txt", ios::app);
    file << "這行會附加在檔案末尾。" << endl;
    file.close();
    return 0;
}
```

執行後：

![image](https://hackmd.io/_uploads/H1rmEXTVle.png)

---

`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：

![image](https://hackmd.io/_uploads/H1538X6Nlx.png)

```cpp=
#include <bits/stdc++.h>

using namespace std;

int main(){

    fstream file("example.txt");

    string s;
    
    file >> s;

    cout << "讀取到的是 : " << s;

    return 0;
}
```

Output：

![image](https://hackmd.io/_uploads/Sy_pIQaEex.png)

這種方式就如同用 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：

![image](https://hackmd.io/_uploads/H1WQwXaElx.png)

### 關閉檔案

當檔案處理完畢後，很重要的一件事是：務必要關上檔案。

語法：

`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)