我有兴趣讨论使用stringstream
解析具有多种类型的行的方法。我将从下面这行开始:
"2.832 1.3067 nana 1.678"
现在让我们假设我有一个有多个strings
和doubles
的长线。解决这个问题的明显方法是对字符串进行标记,然后检查每个字符串的转换情况。我有兴趣跳过第二步,直接使用stringstream
来查找数字。
我认为解决这个问题的一个好方法是读取字符串并检查failbit
是否已设置,如果我尝试将字符串解析为双精度,它将被设置。
假设我有以下代码:
string a("2.832 1.3067 nana 1.678");
stringstream parser;
parser.str(a);
for (int i = 0; i < 4; ++i)
{
double b;
parser >> b;
if (parser.fail())
{
std::cout << "Failed!" << std::endl;
parser.clear();
}
std::cout << b << std::endl;
}
它将打印出以下内容:
2.832
1.3067
Failed!
0
Failed!
0
我并不感到惊讶,它无法解析字符串,但内部发生了什么,以至于它无法清除其failbit
并解析下一个数字?
下面的代码可以很好地跳过坏字并收集有效的double
值
istringstream iss("2.832 1.3067 nana 1.678");
double num = 0;
while(iss >> num || !iss.eof()) {
if(iss.fail()) {
iss.clear();
string dummy;
iss >> dummy;
continue;
}
cout << num << endl;
}
这是一个完整的工作示例。
您的示例几乎是正确的,只是缺少在检测到错误格式后从流中消费无效输入字段
if (parser.fail()) {
std::cout << "Failed!" << std::endl;
parser.clear();
string dummy;
parser >> dummy;
}
在您的示例中,提取将尝试在最后一次迭代中再次从"nana"
读取,因此输出中的最后两行。
还请注意关于iostream::fail()
的欺骗以及如何在我的第一个示例中实际测试iostream::eof()
。有一个众所周知的问题,为什么简单的测试EOF作为一个循环条件被认为是错误的。它很好地回答了当遇到意外/无效值时如何中断输入循环。但是如何跳过/忽略无效的输入字段并没有解释(也没有被要求)。
与π α ντα ρ ε ε的答案有一些细微的区别-使得它也可以处理例如负数表示等,以及- IMHO -更容易阅读。
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh");
double num = 0;
for (; iss; )
if (iss >> num)
std::cout << num << 'n';
else if (!iss.eof())
{
iss.clear();
iss.ignore(1);
}
}
输出:2.832
1.3067
1.678
-100
0.05
我为此建立了一个更精细的版本,它能够跳过无效的输入字符(不需要将double
数字与空白字符分开):
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
istringstream iss("2.832 1.3067 nana1.678 xxx.05 meh.ugh");
double num = 0;
while(iss >> num || !iss.eof()) {
if(iss.fail()) {
iss.clear();
while(iss) {
char dummy = iss.peek();
if(std::isdigit(dummy) || dummy == '.') {
// Stop consuming invalid double characters
break;
}
else {
iss >> dummy; // Consume invalid double characters
}
}
continue;
}
cout << num << endl;
}
return 0;
}
输出
2.832
1.3067
1.678
0.05
现场演示
如果你喜欢简洁-这里有另一个选项(ab?)使用&&
获得cout
只有当一个数字被成功解析,当一个数字没有被解析,它使用逗号操作符能够clear()
流错误状态在条件读取字符之前被忽略…
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh");
double num = 0;
char ignored;
while (iss >> num && std::cout << num << 'n' ||
(iss.clear(), iss) >> ignored)
;
}
http://ideone.com/WvtvfU 您可以像这样使用std::istringstream::eof()
验证输入:
#include <string>
#include <sstream>
#include <iostream>
// remove white-space from each end of a std::string
inline std::string& trim(std::string& s, const char* t = " t")
{
s.erase(s.find_last_not_of(t) + 1);
s.erase(0, s.find_first_not_of(t));
return s;
}
// serial input
std::istringstream in1(R"~(
2.34 3 3.f 3.d .75 0 wibble
)~");
// line input
std::istringstream in2(R"~(
2.34
3
3.f
3.d
.75
0
wibble
)~");
int main()
{
std::string input;
// NOTE: This technique will not work if input is empty
// or contains only white-space characters. Therefore
// it is safe to use after a conditional extraction
// operation >> but it is not reliable after std::getline()
// without further checks.
while(in1 >> input)
{
// input will not be empty and will not contain white-space.
double d;
if((std::istringstream(input) >> d >> std::ws).eof())
{
// d is a valid double
std::cout << "d1: " << d << 'n';
}
}
std::cout << 'n';
while(std::getline(in2, input))
{
// eliminate blank lines and lines
// containing only white-space (trim())
if(trim(input).empty())
continue;
// NOW this is safe to use
double d;
if((std::istringstream(input) >> d >> std::ws).eof())
{
// d is a valid double
std::cout << "d2: " << d << 'n';
}
}
}
这是有效的,因为eof()
检查确保只输入了双精度,而不是像12d4
那样的垃圾。