C++读取文件流时输入运算符重载抛出异常,原因排查
我用C++编写程序,将坐标平面上的点写入文件m.txt(文件内容为(1,1)(2,2)(3,3)(4,4)),再从该文件流读取到vector<point>对象中。写入功能正常,但读取时总是抛出"( expected"错误。
我添加静态变量统计输入函数调用次数,发现错误触发时的调用次数总是比文件中的元素数多1,似乎输入循环会尝试读取超出文件末尾的内容。我对此感到困惑,因为文件流应在其作用域结束时自动关闭。
修改main函数中的读取逻辑为限制读取3个点时,程序可正常输出前3个点;若限制读取4个点,则会触发相同错误。
原完整代码
#include"../dumbheader.h.txt" struct point { int x=0; int y=0; point(int i,int j):x(i),y(j){} point(){} }; istream& operator>>(istream& is, point& p) { // input format accepted (int,int) char ch1, ch2,ch3; if (is >> ch1 && ch1 == '(') { if (!(is >> p.x)) error(" an integer expected \n"); if (!(is >> ch2 && ch2 == ',')) error(" a ',' is expected "); if (!(is >> p.y)) error(" bad second argument \n"); if (!(is >> ch3 && ch3 == ')')) error(" ) expected \n"); } else error(" ( expected "); return is; } ostream& operator<<(ostream& os, const point& p) { os <<'(' <<p.x << "," << p.y<<')'; return os; } vector<point>vp; void printv(vector<point>v) { for (auto x : v) cout << x << endl; } void end_of_loop(istream& ist, char term, const string& message) { if (ist.fail()) { // use term as terminator and/or separator ist.clear(); char ch; if (ist >> ch && ch == term) return; // all is fine error(message); } } int main()try{ vector<point>pp; { ifstream ifs{ "m.txt" }; for (point p; ifs >> p;) pp.push_back(p); } printv(pp); } catch (runtime_error& e) { cout << e.what(); }
修改后的读取逻辑代码
{ ifstream ifs{ "m.txt" }; int i = 1; for (point p; ifs >> p&&i<=3;i++) pp.push_back(p); }
核心原因
输入运算符operator>>没有正确处理**文件结束(EOF)**场景。当读完最后一个点(4,4)后,文件指针已到达末尾,循环条件ifs >> p会再次调用operator>>。此时is >> ch1读取失败(无数据可读),直接进入else分支抛出"( expected"错误——这就是为什么调用次数比元素数多1的原因。
循环的逻辑是:只要流处于有效状态,就会尝试读取。读完4个点后,第5次调用operator>>时,流已到EOF,读取操作失败,触发错误。
修复方案
修改operator>>,在读取前检查流是否已到EOF,若是则直接返回流,不抛出错误;只有当流未到EOF但读取格式错误时,才抛出异常。
修改后的输入运算符代码:
istream& operator>>(istream& is, point& p) { char ch1, ch2, ch3; // 先判断是否已到文件末尾,是则直接返回 if (is.eof()) { return is; } if (is >> ch1 && ch1 == '(') { if (!(is >> p.x)) error(" an integer expected \n"); if (!(is >> ch2 && ch2 == ',')) error(" a ',' is expected "); if (!(is >> p.y)) error(" bad second argument \n"); if (!(is >> ch3 && ch3 == ')')) error(" ) expected \n"); } else { // 仅当未到文件末尾时才抛出格式错误 if (!is.eof()) { error(" ( expected "); } } return is; }
另一种更简洁的写法,通过串联读取操作判断流状态,仅在非EOF的格式错误时抛出异常:
istream& operator>>(istream& is, point& p) { char ch1, ch2, ch3; // 串联所有读取步骤,任一失败则进入else分支 if (is >> ch1 && ch1 == '(' && is >> p.x && is >> ch2 && ch2 == ',' && is >> p.y && is >> ch3 && ch3 == ')') { // 读取成功,无需额外操作 } else { // 仅当未到文件末尾时,才抛出格式错误 if (!is.eof()) { error("invalid point format"); } } return is; }
修改后,当流到达EOF时,operator>>会直接返回,流进入失效状态,循环for (point p; ifs >> p;)会自动终止,不会触发错误。
内容的提问来源于stack exchange,提问作者Abdullah Othman

