C++中parser.hpp的csv类parse返回值在main中size为0的问题解决
问题根因
使用同一个ifstream对象反复读取文件时,没有重置流的状态和位置指针:
第一次调用parse时,getline循环会完整读完整个文件内容,将文件指针停在末尾位置,同时给流打上EOF标记。后续所有基于同一流的读取操作,只要不清除EOF标记、不把指针拉回文件开头,getline会第一时间读取到文件结束标识,直接退出循环,最终返回空的vector,表现为返回的size始终为0。
解决办法
不改动原有类结构的最小修复
如果要保持现有parse、print_parsed_csv均接收文件流的设计,每次调用依赖文件流的方法前,必须先完成两步流重置操作:
- 调用
file.clear()清除流的EOF/错误标记(不执行这一步,哪怕指针挪回开头,所有读操作都会直接失败) - 调用
file.seekg(0, ios::beg)将文件指针重新定位到文件开头
调整后的main.cpp参考实现(顺带删掉了原代码中无法通过编译的笔误行cout << parsed_csv_data.();):
int main() { ifstream file; file.open("test.csv"); // 补充文件打开校验,避免路径错误、权限不足导致读空的问题 if (!file.is_open()) { cout << "文件无法打开" << endl; return 1; } csv obj; // 第一次读取时流刚打开,指针在文件开头,可正常解析 obj.parse(file); // 调用print_parsed_csv前重置流状态 file.clear(); file.seekg(0, ios::beg); obj.print_parsed_csv(file); // 获取parse返回值前再次重置流状态 file.clear(); file.seekg(0, ios::beg); vector<vector<string>> parsed_csv_data = obj.parse(file); cout << parsed_csv_data.size() << endl; for (int i = 0; i < parsed_csv_data.size(); i++) { for (int j = 0; j < parsed_csv_data[i].size(); j++) cout << parsed_csv_data[i][j] << '\t'; cout << endl; } }
这种方案虽然能解决问题,但非常容易漏操作——后续新增其他调用parse的逻辑时,忘了重置流就会再次出现返回空vector的bug。
更合理的结构优化(从根源避免问题)
现有类设计完全冗余:解析一次CSV只需要读取一遍文件,没必要每次打印、每次取结果都重新遍历文件。可以把解析结果存为类的私有成员,parse仅负责读一次文件、把结果存入成员变量,后续的打印、取值操作直接使用缓存的结果,从根源上避免反复操作文件流的隐患。
优化后的parser.hpp参考实现:
#include <vector> #include <string> #include <fstream> #include <iostream> using namespace std; class csv { private: // 缓存已解析的CSV结果,避免重复读文件 vector<vector<string>> parsed_data; public: vector<vector<string>> parse(ifstream &file) { parsed_data.clear(); string line; while (getline(file, line)) { vector<string> parsed_line; // 处理空行的边界情况 if (line.empty()) { parsed_data.push_back(parsed_line); continue; } string current_str = line; while (!current_str.empty()) { // 使用size_t接收find返回值,直接和string::npos比较,避免有符号/无符号转换的隐患 size_t delimiter_pos = current_str.find(','); if (delimiter_pos == string::npos) { parsed_line.push_back(current_str); break; } string cell = current_str.substr(0, delimiter_pos); current_str = current_str.substr(delimiter_pos + 1); parsed_line.push_back(cell); } parsed_data.push_back(parsed_line); } return parsed_data; } // 打印方法无需传入文件流,直接读取类内缓存的结果 void print_parsed_csv() { cout << parsed_data.size() << endl; for (size_t i = 0; i < parsed_data.size(); i++) { for (size_t j = 0; j < parsed_data[i].size(); j++) cout << parsed_data[i][j] << '\t'; cout << endl; } } // 提供单独的取结果接口,无需反复调用parse vector<vector<string>> get_parsed_data() { return parsed_data; } };
调整后的main.cpp逻辑更简洁,也不需要反复操作流状态:
int main() { ifstream file("test.csv"); if (!file.is_open()) { cout << "文件无法打开" << endl; return 1; } csv obj; // 仅读取一次文件,将解析结果存在类内部 obj.parse(file); // 直接调用打印方法 obj.print_parsed_csv(); // 直接获取缓存的解析结果 vector<vector<string>> parsed_csv_data = obj.get_parsed_data(); cout << parsed_csv_data.size() << endl; for (size_t i = 0; i < parsed_csv_data.size(); i++) { for (size_t j = 0; j < parsed_csv_data[i].size(); j++) cout << parsed_csv_data[i][j] << '\t'; cout << endl; } }
原代码存在的其他隐患
- main.cpp中的
cout << parsed_csv_data.();是未完成的笔误,会直接导致编译失败,需要删除 - 原代码中使用
int类型接收string::find()的返回值存在风险:find()返回的是无符号size_t类型,找不到内容时返回的string::npos是极大的无符号值,转成int虽然在大部分平台上会变成-1,但属于类型不匹配的隐患,应当使用size_t接收返回值,直接与string::npos做比较 - 原代码没有校验文件打开状态,当文件路径错误、无读取权限时,第一次读取就会返回空vector,排查难度较高
内容的提问来源于stack exchange,提问作者user15346521
相关产品推荐
相关产品推荐

