先讀語法與例子,再操作互動圖解並完成四選一自測。忘記寫法時回看速查表,最後用中英詞彙卡複習。
本頁先讀寫文字檔,再學習開檔失敗、界線檢查與例外傳遞。檔案範例可以獨立執行,不需其他教材資料。這些做法也適用於一般程式的輸入驗證與錯誤回報。
檔案串流物件會管理它開啟的檔案。物件生命週期結束時,會執行稱為解構式(destructor)的收尾函式。先理解「物件負責管理資源」即可;本頁使用標準類別,不需要先自行撰寫解構式。
ofstream 寫、ifstream 讀,用法跟 cout/cin 一模一樣 —— 因為它們是同一家族。
1: READ 2: CHECK 3: SEND
三個重點:① 檔案串流需 <fstream>。② getline 回傳串流參考,while 依讀取狀態判斷是否繼續;最後一行沒有換行也可成功讀取。③ 物件正常離開作用域時解構式會關檔,但本例要在 out 還活著時立刻讀同一檔案,所以先明確 close,確保寫入完成。
| 寫法 | 意思 |
|---|---|
| ifstream in("a.txt"); | 開檔讀取 |
| ofstream out("a.txt"); | 開檔寫入,清空原內容 |
| ofstream out("a.txt", ios::app); | 附加到結尾 |
| getline(in, line) | 讀取一行,回傳串流;作條件時反映這次讀取是否成功 |
| in >> word | 讀一個「詞」,以空白切開 |
stringstreamstringstream 是這一家族的第三個成員:把字串當成 cin 來讀,>> 一樣自動以空白切詞、照變數型別轉換。跟 P1 用 istringstream 模擬鍵盤輸入是同一家,只是 stringstream 讀寫都能做。
[pencil] [blue] [12]
stringstream 讓字串也能用串流的 >> 規則逐項解析,並依目標型別轉換。獨立程式應明確 #include <sstream>,不要依賴特定環境自動引入。
is_open() = 0 檔案不存在時 ifstream 不會丟例外,只是進入失敗狀態。 所以開檔之後一定要自己檢查。
它不丟例外、不終止、什麼都不說,只是把串流設成失敗狀態。你如果不檢查就直接讀,會讀到零筆資料然後難以判斷原因。所以 ifstream 開完一定要接一句 if (!in.is_open())。
檔案讀取失敗時,先確認工作目錄、檔名及開檔狀態,再檢查資料格式。相對路徑以執行時的工作目錄為基準。
例外處理的骨架只有兩個關鍵字:try 圈出可能失敗的程式,catch 接住失敗。
下面先看語法骨架:risky() 代表可能失敗的函式,SpecificError 代表要捕捉的例外型別,兩者都是占位名稱,不是標準函式或可直接執行的程式。後面的完整範例會換成 readAge 與 invalid_argument。
| C++ | Python | 備註 |
|---|---|---|
| try { } catch (…) { } | try: / except: | 概念相同 |
| throw X(); | raise X() | 主動拋出 |
| e.what() | str(e) | 取得訊息 |
| 沒有對應 | finally: | 把資源綁定到物件生命週期,由解構式收尾:RAII(Resource Acquisition Is Initialization) |
| 沒有對應 | else: | C++ 沒有這個區塊 |
catch (...) 把所有東西一起接住catch (...) 接住所有傳到此處的 C++ 例外,但不能處理一般的未定義行為或記憶體存取錯誤。它可用於清理後重新拋出,或在程式邊界統一回報;若接住後不記錄、不處理,就可能掩蓋問題。
同一段程式,餵不同的輸入就會走出兩條完全不同的路。按下面兩顆 ▶ 各跑一次,注意例外那條路上 readAge 的框是什麼時候消失的。
例外不是從 throw 直接跳到 catch。它會沿著呼叫鏈往回走,把中間每一個函式的框架逐一撤掉,撤掉時該框架裡區域物件的解構式都會被呼叫 —— 這叫 stack unwinding。所以檔案串流這類物件不必手動收尾,例外飛過去的時候它們也會自己關好。
catch 只接住型別相符的例外。丟 invalid_argument、只寫 catch (const out_of_range& e),例外就會穿過去繼續往上飛;一路到 main 外面都沒人接,程式直接 terminate 終止。父類別接得住子類別,所以 catch (const exception& e) 是最寬的那一層,要放最後面。
trying at: caught out_of_range details: (由實作提供的錯誤描述) continue
vector 的 v[10] 不符合有效索引前提,屬未定義行為,所以只留在註解中。實際執行的是 v.at(10):它丟出 out_of_range,catch 接住後可繼續執行。what() 訊息依實作而異;trying at: 已在拋出之前輸出,例外不會回復先前的副作用。
| 常見標準例外 | 什麼時候發生 |
|---|---|
| out_of_range | vector::at()、string::at() 越界 |
| invalid_argument | stoi("abc") 這種轉不了的輸入 |
| bad_alloc | new 要不到記憶體 |
| runtime_error | 通用的執行期錯誤,自訂例外常繼承它 |
不確定索引時,at() 的範圍檢查能讓錯誤較早被發現。已確認合法且需要控制檢查成本時可用 [];兩者差別是介面契約,不是「所有程式都該用某一個」。string 的 size() 位置另可讀結尾空字元,不能把它和 vector 的界線完全混同。
函式無法接受輸入時,可以透過例外回報。本例遇到負數就拋出 invalid_argument;正常路徑只回傳已通過檢查的數量。
數量不可為負數
先用標準的 invalid_argument 表達輸入不合法,throw 會離開目前正常路徑並尋找符合的 catch。等學過類別、繼承與模板後,P9 保留完整自訂例外與草稿箱例子。
特殊回傳值是否合適,取決於函式契約。例如本頁數量只允許非負值,便可另約定以 -1 表示錯誤;但若函式回傳溫度,-1 也可能是正常資料。採特殊值時,呼叫端必須檢查;採例外時,則由相符的 catch 處理。這裡使用 invalid_argument,讓正常回傳值只表達有效數量。
接下來這行會丟例外,而且沒有人接: [執行結束碼 -6] terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 99) >= this->size() (which is 3)
程式會終止,後面的正常敘述不再執行。這裡的 terminate 診斷與結束碼是特定環境的示例;不能只憑 -6 判定是未捕捉例外或 assert,因為其他情況也可能呼叫 abort。定位原因時應一起閱讀例外型別、訊息與發生位置。
在有能力處理或轉譯錯誤的位置使用 catch。若目前這一層無法處理,可讓例外繼續傳遞,交由呼叫端或程式邊界統一回報。保留錯誤資訊,避免無聲忽略後繼續使用不完整的結果。
ifstream in("data.txt"); 檔案不存在時會發生什麼?
你在 debug 一個索引算錯的排序程式。應該把 v[i] 暫時換成什麼?
自訂例外需要類別與繼承語法,完整寫法請接著讀 P9 的自訂例外。本頁速查先整理已學過的標準例外。
① ifstream 開完一定要檢查 is_open()。
② 相對路徑是相對於執行時的工作目錄。
③ debug 時把 [] 換成 at()。
④ 在適當的位置處理例外;無法處理時,保留資訊並向呼叫端傳遞。
| 資源 | 看什麼 |
|---|---|
| cppreference · fstream | 開檔模式與串流狀態的完整說明。 |
| cppreference · Exceptions | 標準例外的完整階層。 |
| 本站 | 程式唯一示範例外處理的地方。 |
每個選項都有解說:選錯也點開看看為什麼錯。全對之後再往下翻詞彙卡。
vector<int> v = {10, 20, 30};
try {
cout << v.at(10);
} catch (const out_of_range& e) {
cout << "接住了";
}
cout << "|繼續";
ofstream out("log.txt"); // log.txt 原本有 100 行
out << "新的一行" << endl;
out.close();
ifstream in("log.txt");
string line;
int n = 0;
while (getline(in, line)) n++;
cout << n;
先看正面術語,心中默想定義再翻面對答案;洗牌後再過一輪,直到每張都能不假思索說出來。