我有兴趣讨论使用stringstream
解析具有多种类型的行的方法。我将从查看以下内容开始:
"2.832 1.3067 nana 1.678"
现在假设我有一个包含多个strings
和doubles
的长行。解决这个问题的显而易见的方法是将字符串标记化,然后检查转换每个字符串。我有兴趣跳过第二步并直接使用stringstream
来查找数字。
我认为解决这个问题的好方法是读取字符串并检查failbit
是否已设置,如果我尝试将字符串解析为double,它将会是这样。
说我有以下代码:
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
并解析下一个数字?
答案 0 :(得分:19)
以下代码可以跳过坏词并收集有效的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()
。 There's a well known Q&A,为什么简单测试EOF作为循环条件被认为是错误的。它答案很好,如何在遇到意外/无效值时打破输入循环。但是那里没有解释如何跳过/忽略无效的输入字段(并且没有被要求)。
答案 1 :(得分:3)
对πάνταῥεῖ的答案几乎没有什么微小差异 - 使它也可以处理,例如负数表示等,以及 - 恕我直言 - 更容易阅读。
#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
(见运行here)
答案 2 :(得分:2)
我为此构建了一个更精细的调整版本,能够明智地跳过无效输入字符(无需用空格字符分隔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
答案 3 :(得分:2)
如果你喜欢简洁 - 这是另一种选择(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)
;
}
答案 4 :(得分:1)
您可以使用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
之类的垃圾。