HOWTO · C++
在 C++ 中讀取 CSV 檔案
本文演示瞭如何在 C++ 中讀取 CSV 檔案
本頁內容
在 C++ 中讀取簡單的逗號分隔檔案時,可以使用 std::getline 一次讀取一行,再以第二次 std::getline 呼叫將該行拆分為欄位。這種做法適用於每個實體行都是一筆記錄,且逗號只用來分隔欄位的情況。下方範例會自行建立一個小型輸入檔,因此不必另外準備資料檔,就能編譯並執行。
使用 std::getline 和 std::istringstream 讀取 CSV 檔案
程式會先將兩筆記錄寫入 grades.csv,再開啟檔案讀取。外層的 std::getline 會將一個實體行讀入 std::string;接著,std::istringstream 將該行轉換為串流,而內層的 std::getline 則使用逗號作為分隔符,逐一擷取欄位。每筆記錄會存成一個 std::vector<std::string>,所有記錄則存放在另一個向量中。如此可保留原有順序;當各列只是依序排列時,也不必使用以整數為鍵的 map。
請執行這個自含式程式。程式碼及其中顯示的標籤與輸出維持英文:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
std::string trimEnds(const std::string& value) {
const auto first = std::find_if_not(value.begin(), value.end(),
[](unsigned char ch) { return std::isspace(ch); });
const auto last = std::find_if_not(value.rbegin(), value.rend(),
[](unsigned char ch) { return std::isspace(ch); }).base();
if (first >= last) {
return "";
}
return std::string(first, last);
}
int main() {
const std::string path = "grades.csv";
{
std::ofstream sample(path);
if (!sample) {
std::cerr << "Could not create input file\n";
return 1;
}
sample << "Name, Score\nAda Lovelace, 98 \nGrace Hopper, 95\n";
}
std::ifstream input(path);
if (!input) {
std::cerr << "Could not open input file\n";
return 1;
}
std::vector<std::vector<std::string>> rows;
std::string record;
while (std::getline(input, record)) {
std::istringstream line(record);
std::vector<std::string> fields;
std::string field;
while (std::getline(line, field, ',')) {
fields.push_back(trimEnds(field));
}
rows.push_back(fields);
}
for (const auto& row : rows) {
std::cout << row[0] << " | " << row[1] << '\n';
}
return 0;
}
以下是成功執行時提供的結果:
Name | Score
Ada Lovelace | 98
Grace Hopper | 95
建立檔案或開啟檔案失敗時,程式都會將錯誤訊息寫入 std::cerr,並回傳狀態碼 1;正常執行則回傳狀態碼 0。在自己的程式中,讀取前應先確認輸入串流是否成功開啟。如果檔案來自程式外部,請指出檔案路徑,並依應用程式的需求處理開啟失敗;不要假設檔案一定存在。
處理 CSV 欄位中的空白
是否處理空白是資料處理上的選擇,並非拆分欄位時會自動完成的步驟。trimEnds 只會移除每個擷取欄位開頭與結尾的空白,欄位內的空格則會保留,例如 Ada Lovelace 中的空格。呼叫 std::isspace 前先轉換為 unsigned char,可避免將值為負數的普通 char 傳入這個字元分類函式。
有些檔案會把逗號後的空格視為格式排版,另一些則會將它視為欄位內容的一部分。只有在符合你要保留的資料規範時,才應修剪空白。移除所有空白字元也會把姓名或地址中的字詞連在一起;修剪兩端則會改變前置或尾隨空白的內容。如果來源資料中的空白具有意義,請保留讀取到的欄位,或採用更精確的規則,例如只修剪 ASCII 空格。
拆分迴圈也會影響空欄位的處理方式:其他內容之間若有一對相鄰逗號,中間會產生空字串。行尾的逗號不會讓這個 std::getline 迴圈附加最後一個空欄位;如果輸入格式要求保留行尾空欄位,請明確處理。請依預期欄位決定要保留還是拒絕空值。如果某行的欄位數少於標頭列,應在索引該列之前明確處理;上方簡短的顯示迴圈假設兩個欄位都存在。
了解以分隔符解析的限制
這種方法會在每個實體行的每個逗號處切分內容,但不會解析 CSV 引號。例如,"Paris, France" 這樣的值會被切成兩段,儘管 CSV 讀取器應將引號內的文字視為單一欄位。引號欄位內的跳脫引號也不會被解碼,而欄位內的換行則會被誤認為記錄結尾。每筆記錄的欄位數也可能不同,因此不要假設每行的逗號數都相同。
如果輸入內容受控且簡單,並能確保每個實體行只有一筆記錄且不含引號括住的分隔欄位,就可以使用這種做法。對一般 CSV 檔案,請使用能依照實際格式規則處理引號與跳脫方式的 CSV 解析器。完成解析後,仍應驗證應用程式所需欄位的數量與內容。這裡示範的標準串流操作可讀取檔案與行,但本身不會實作完整的 CSV 格式語法。