C++14中重载提取运算符如何处理可选空值?
问题
我需要用数据填充一个简单结构体:
struct base { int a; std::string s1; std::string s2; int b; };
为此我实现了提取运算符(以及用于调试的插入运算符):
friend std::ostream& operator<<(std::ostream& os, const base& b) { os << "a = " << b.a << "\n" << "s1='" << b.s1 << "'\n" << "s2='" << b.s2 << "'\n" << "b = " << b.b; return os; } friend std::istream& operator>>(std::istream& is, base& b) { std::cout << "is.good: " << is.good() << ", is.eof: " << is.eof() << ", in_avail:" << is.rdbuf()->in_avail() << std::endl; is >> b.a; std::cout << "is.good: " << is.good() << ", is.eof: " << is.eof() << ", in_avail:" << is.rdbuf()->in_avail() << std::endl; is >> b.s1; std::cout << "is.good: " << is.good() << ", is.eof: " << is.eof() << ", in_avail:" << is.rdbuf()->in_avail() << std::endl; is >> b.s2; std::cout << "is.good: " << is.good() << ", is.eof: " << is.eof() << ", in_avail:" << is.rdbuf()->in_avail() << std::endl; is >> b.b; return is; }
正常测试用例basevalue1("1\nhello\nworld\n2")可以正确解析,但当测试用例为basevalue2("1\nhello\n\n2")时,提取运算符会跳过空字段,将s2赋值为"2",而b则保留垃圾值。我需要让"2"正确赋值给b,同时s2保持为空。
我尝试过自定义分隔符的ctype,但没有效果。使用的编译器版本为C++14,请问如何在提取时正确处理空字段?
解决方案
问题出在std::istream::operator>>处理字符串时会自动跳过所有空白字符(包括换行、空格、制表符等),并且只读取到下一个空白字符为止。遇到空行时,它会直接跳过,继续读取后面的非空白内容,导致s2被错误赋值为"2"。
要正确处理按行分割的空字段,改用std::getline是最直接的方案,只需注意处理整数读取后残留的换行符:
- 读取整数后清除换行符:用
operator>>读取a之后,输入流中会残留一个换行符,必须先处理掉,否则第一个getline会读取到空字符串。可以用std::ws来跳过所有前导空白(包括换行)。 - 用getline读取每行字符串:
getline会读取到换行符为止,空行对应的就是空字符串,正好符合空字段的需求。 - 读取最后一个整数:读取完
s2后,直接用operator>>读取b即可,getline已经吃掉了前面的换行符,不会影响整数读取。
修改后的提取运算符代码如下:
friend std::istream& operator>>(std::istream& is, base& b) { // 读取整数a并跳过后续空白(包括换行) is >> b.a >> std::ws; // 读取s1所在行,空行则s1为空 std::getline(is, b.s1); // 读取s2所在行,空行则s2为空 std::getline(is, b.s2); // 读取整数b is >> b.b; return is; }
如果需要保留调试输出,可以将状态打印代码插入到对应位置,但核心逻辑要按上述方式修改。
自定义ctype的方式并不适合这个场景,因为它会全局改变流的空白处理规则,容易影响其他输入操作,且实现复杂度远高于getline方案。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

