Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 13

檔案操作

用 ofstream 寫檔、ifstream 讀檔與錯誤檢查
Interaction實作 20 minute(s)

Learning Objectives

  • 用 ofstream 建立檔案並寫入,分辨覆寫與 ios::app 附加
  • 用 ifstream 開啟檔案,並檢查是否開啟成功
  • 用 >> 逐筆讀取、用 getline 逐行讀取,寫出讀到檔案結尾就停的迴圈
  • 說明 close 的作用,以及檔案物件結束時會自動關檔
  • 讀取 CSV 格式的資料,用 stringstream 依逗號拆出欄位

為什麼要讀寫檔案

到目前為止,程式的資料都放在變數裡;程式一結束,資料就不見了。要讓資料留下來,例如成績紀錄、設定值、遊戲存檔, 就要寫進檔案;要處理別人準備好的大量資料,例如一份 CSV 成績表,就要從檔案讀進來。

C++ 用檔案串流(file stream)讀寫檔案,標頭檔是 <fstream>。它的用法和你熟悉的 cout、cin 幾乎一樣:

類別 用途 類比
ofstream 寫入檔案(output file stream) 像 cout,用 <<
ifstream 讀取檔案(input file stream) 像 cin,用 >> 或 getline
fstream 同一個檔案又讀又寫
ofstream out("todo.txt");   // 開啟(建立)檔案
out << "買牛奶" << endl;     // 和 cout 一樣的寫法
out.close();                // 關閉檔案

本課的程式在一個沙盒資料夾裡執行,範例會先放好要讀的檔案;程式寫出的檔案,會在輸出區多一個分頁顯示它的內容。

ofstream 寫檔與 ios::app 附加

右邊的變數區會顯示每個檔案串流連到哪個檔案,以及目前是「開啟中」還是「已關閉」。播完之後切到輸出區的檔案分頁:todo.txt 有三行(第三行是附加上去的),study.txt 是迴圈寫出來的。

開檔模式與檢查

開啟檔案時可以用第二個參數指定模式(定義在 ios 裡,可以用 | 組合):

模式 意思
ios::out 寫入(ofstream 的預設)
ios::in 讀取(ifstream 的預設)
ios::trunc 開啟時清空原本的內容(ofstream 預設就會清空)
ios::app 附加:每次寫入都接到檔案結尾
ios::binary 二進位模式,不轉換換行字元

開檔不一定成功:要讀的檔案不存在、檔名打錯、沒有權限都會失敗。失敗時不會丟出例外,只是串流變成失敗狀態, 接下來的讀寫全部沒有作用。所以開檔之後要檢查:

ifstream in("scores.txt");
if (!in.is_open()) { ... }   // 寫法一:is_open()
if (!in) { ... }             // 寫法二:串流放在條件裡,失敗時是 false

檔案路徑:只寫檔名時,是相對於程式執行時的「工作目錄」,不一定是程式碼所在的資料夾。在 Windows 上寫完整路徑時, 反斜線要寫兩次("C:\\data\\a.txt"),或改用斜線 "C:/data/a.txt"。

用 >> 逐筆讀到檔案結尾

scores.txt 每行有一個名字和一個分數。第 16 行的 while (in >> name >> score) 每一圈讀一個名字和一個分數;讀到檔案結尾、讀不到東西時,條件變成 false,迴圈結束。注意 >> 會自動跳過空白和換行。

getline 逐行讀取

notes.txt 不存在,所以第 8 行開檔失敗,變數區會顯示「開啟失敗」。poem.txt 的第一行中間有一個空白:用 getline 讀時整行完整讀進來;最後用 >> 讀,只讀到空白之前。

想一想:寫入與讀取

1.

下面的程式會印出什麼?

ofstream a("memo.txt");
a << "A";
a.close();
ofstream b("memo.txt");
b << "B";
b.close();

ifstream in("memo.txt");
string s;
in >> s;
cout << s;
2.

檔案 data.txt 的內容是:

hello world
second line

下面的程式會印出什麼?

ifstream in("data.txt");
string line, word;
getline(in, line);
in >> word;
cout << line << "|" << word;
3.

ifstream in("nofile.txt"); 而這個檔案不存在,會發生什麼事?

第 1 次作答

讀到檔案結尾的正確寫法

讀取檔案最常用的兩種迴圈:

while (in >> x) { ... }            // 一筆一筆讀,以空白或換行分隔
while (getline(in, line)) { ... }  // 一行一行讀

讀取動作本身就是條件:讀成功是 true,讀到檔案結尾(或格式不符,例如要讀 int 卻遇到文字)就是 false。

初學者常寫成下面這樣,最後一筆常常會被處理兩次:

while (!in.eof()) {      // 不建議
    in >> x;
    sum += x;            // 最後一次 in >> x 失敗了,x 還是上一筆的值
}

eof() 要等到「已經讀過頭」才會變成 true,所以判斷的時間點晚了一步。用讀取動作當條件就沒有這個問題。

close() 要不要寫? 檔案串流物件被解構時(例如離開所在的函式)會自動關檔。明確寫 close() 的好處是: 寫入的資料立刻存進檔案,同一個程式接著要用另一個串流讀這個檔案時,才讀得到完整內容;也可以讓同一個串流物件再 open() 另一個檔案。

CSV 與 stringstream

CSV(comma-separated values)是最常見的表格資料格式,試算表軟體都能匯出:每一行是一筆資料,欄位之間用逗號分隔,第一行通常是標題。

name,chinese,math
Amy,85,92

>> 以空白分隔,沒辦法直接拆逗號。做法分兩步:

  1. 用 getline(in, line) 讀一整行
  2. 把這一行放進 stringstream(標頭檔 <sstream>),它是「把字串當成串流」的類別,再用 getline(ss, field, ',') 一次讀到逗號為止

getline 的第三個參數是分隔字元,預設是換行 '\n'。拆出來的欄位都是 string,數字要用 stoi(整數)或 stod(浮點數)轉換。

讀取 CSV 並計算總分

每一圈先讀一行到 line,再建立 stringstream ss 拆成三個欄位。注意變數區裡 chinese、math 帶引號,表示它們是字串,要到第 18 行才轉成數字。

想一想:逐行讀取與拆欄位

1.

下面的程式會印出什麼?

stringstream ss("蘋果,,香蕉");
string field;
int count = 0;
while (getline(ss, field, ',')) {
    count++;
}
cout << count;
2.

檔案 nums.txt 的內容是 3 5 x 7(一行)。下面的程式會印出什麼?

ifstream in("nums.txt");
int n, sum = 0;
while (in >> n) {
    sum += n;
}
cout << sum;
第 1 次作答

動手寫:購物清單小計

沙盒裡已經有 cart.csv:第一行是標題,之後每行是「品名,單價,數量」。完成迴圈,印出每個品項的小計(單價乘數量),最後印出合計。

item,price,qty
筆記本,45,3
原子筆,15,4
便當,80,1
你的輸出
預期輸出

重點回顧

  • 檔案串流在 <fstream>:ofstream 寫、ifstream 讀,用法和 cout、cin 一樣
  • ofstream 預設清空原本的內容;要接在後面寫,用 ios::app
  • 開檔失敗不會丟出例外,開檔後用 is_open() 或 if (!in) 檢查
  • >> 以空白分隔逐筆讀,getline 逐行讀(保留行內的空白);用 while (in >> x)、while (getline(in, line)) 讀到檔案結尾,不要用 while (!in.eof())
  • close() 把資料寫進檔案並結束連線;串流物件解構時也會自動關檔
  • CSV:getline 讀一行,放進 stringstream,再用 getline(ss, field, ',') 拆欄位,數字用 stoi、stod 轉換
這一課完成了!
Next lesson:動態記憶體
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。