在 C++ 中处理 CSV:从手动解析到 libcsv 库
CSV(逗号分隔值)格式仍然是交换表格数据最流行的方法之一。C++ 没有内置的 CSV 支持,但存在几种有效的方法:使用标准工具进行手动解析,以及使用专门的库,例如 libcsv。在本文中,我们将分析这两种方法、它们的优缺点,并提供可运行的代码示例。
什么是 CSV 及其特点
CSV 是一种文本格式,其中每一行对应一条记录,字段之间用逗号分隔。主要难点在于:
- 如果字段用引号括起来,则可以包含逗号(
"Hello, world"); - 字段内的引号通过重复来转义(
""); - 分隔符可以是其他字符(分号、制表符);
- 引号内的字符串可以包含换行符。
因此,简单地按逗号分割字符串常常会导致错误。让我们考虑两种可靠的方法。
方法 1:使用 std::stringstream 手动解析
适用于没有嵌套引号和换行的简单 CSV。实现包括两个函数:读取单个字段和解析整行。
#include <iostream>#include <fstream>#include <sstream>#include <vector>#include <string>
// 将 CSV 行分割成字段,考虑引号std::vector<std::string> parseCSVLine(const std::string& line) { std::vector<std::string> fields; std::string field; bool inQuotes = false;
for (size_t i = 0; i < line.size(); ++i) { char c = line[i]; if (c == '"' && !inQuotes) { inQuotes = true; } else if (c == '"' && inQuotes) { // 检查转义引号 if (i + 1 < line.size() && line[i + 1] == '"') { field += '"'; ++i; // 跳过下一个引号 } else { inQuotes = false; } } else if (c == ',' && !inQuotes) { fields.push_back(field); field.clear(); } else { field += c; } } fields.push_back(field); // 最后一个字段 return fields;}
int main() { std::ifstream file("data.csv"); std::string line;
while (std::getline(file, line)) { auto fields = parseCSVLine(line); for (const auto& f : fields) { std::cout << "[" << f << "] "; } std::cout << std::endl; } return 0;}优点: 无外部依赖,完全控制逻辑。
缺点: 不处理多行字段(包含 的值),需要手动错误处理。
方法 2:libcsv 库
libcsv 是一个轻量级的 C 语言 CSV 解析库,用 C 编写,但可以很好地与 C++ 配合使用。它能正确处理所有标准情况:引号、转义、多行字段、自定义分隔符。
安装
从 SourceForge 下载源码,或通过包管理器安装:
# Linux (Debian/Ubuntu)sudo apt-get install libcsv-dev
# macOSbrew install libcsv使用 libcsv 读取 CSV 的示例
#include <iostream>#include <fstream>#include <vector>#include <string>#include <cstring>#include <csv.h>
// 用于存储数据的结构体struct CSVData { std::vector<std::vector<std::string>> rows; std::vector<std::string> currentRow;};
// Ca