如何测试字符串流运算符>>是否解析了错误类型并跳过它



我有兴趣讨论使用stringstream解析具有多种类型的行的方法。我将从下面这行开始:

"2.832 1.3067 nana 1.678"

现在让我们假设我有一个有多个stringsdoubles的长线。解决这个问题的明显方法是对字符串进行标记,然后检查每个字符串的转换情况。我有兴趣跳过第二步,直接使用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那样的垃圾。

相关内容

最新更新