如何使用ifstream正确读取文件中的unsigned int变量?

Luc*_*Man 7 c++ file ifstream

我的代码从文本文件中读取unsigned int变量Input_File_Name.

unsigned int Column_Count; //Cols
unsigned int Row_Count;//Rows

try {
    ifstream input_stream;
    input_stream.open(Input_File_Name,ios_base::in);
    if (input_stream) {
        //if file is opened
        input_stream.exceptions(ios::badbit | ios::failbit);
        input_stream>>Row_Count;
        input_stream>>Column_Count;


    } else {
        throw std::ios::failure("Can't open input file");
        //cout << "Error: Can't open input file" << endl;
    }

} catch (const ios::failure& error) {
    cout << "Oh No!!" << error.what() << endl;          
} catch (const exception& error) {
    cout << error.what() <<"Oh No!!" << endl;
} catch (...) {
    cout << "Unknown exception" << endl;
}
Run Code Online (Sandbox Code Playgroud)

它非常棒.但是当我用错误的数据填充文本文件时

33abcd4  567fg8
Run Code Online (Sandbox Code Playgroud)

它以这样的方式工作:

input_stream>>Row_Count; //Row_Count = 33;
input_stream>>Column_Count; // throws an ios::failure exception
Run Code Online (Sandbox Code Playgroud)

为什么这行不会input_stream>>Row_Count;抛出异常?据我所知,input_stream将任何非数字符号视为分隔符,并在下一步尝试读取"abcd".是这样吗?如何设置空格符号作为分隔符,以便在读取"33abcd4"时ios::failure从这行代码中抛出异常input_stream>>Row_Count;

Die*_*ühl 4

如果流可以读取任何整数值,则整数值的正常提取会成功。也就是说,如果至少有一位数字可选地后跟任何内容,则整数读取成功。正常的提取操作不会尝试读取更多内容,特别是它们不会尝试查找下一个空格。

从它的声音来看,您需要确保您的号码后面有一个空格,如果没有则失败。我可以想到两种不同的方法来做到这一点:

  1. 创建一个简单的操纵器,用于检查流是否位于空白字符上。然而,这意味着您将使用类似的东西来读取您的值in >> value >> is_space
  2. 创建一个自定义std::num_get<char>方面,将其安装到 中std::locale,然后imbue()将其安装std::locale到您的流中。它涉及更多一些,但不需要对整数的读取方式进行任何更改。

创建这样的操纵器相当简单:

std::istream& is_space(std::istream& in)
{
    if (!std::isspace(in.peek()))
    {
        in.setstate(std::ios_base::failbit);
    }
    return in;
}
Run Code Online (Sandbox Code Playgroud)

现在,改变数字的读取方式更有趣,我怀疑我刚刚命名了许多大多数人都不太了解的标准库类。因此,让我们也快速为此键入一个示例。我将仅更改std::num_get<char>方面来处理unsigned int:要对其他整数类型执行此操作,有必要覆盖更多函数。因此,这是该std::num_get<char>方面的替代品:

class num_get:
    public std::num_get<char>
{
    iter_type do_get(iter_type it, iter_type end,
                     std::ios_base& ios, std::ios_base::iostate& err,
                     unsigned int& value) const
    {
        it = std::num_get<char>::do_get(it, end, ios, err, value);
        if (it != end && !isspace(static_cast<unsigned char>(*it)))
        {
            err |= std::ios_base::failbit;
        }
        return it;
    }
};
Run Code Online (Sandbox Code Playgroud)

这一切所做的就是从一个类派生std::num_get<char>并重写它的一个虚函数。这个函数的实现相当简单:首先通过委托给基类来读取值(我刚刚意识到虚拟函数确实想要保护而不是像我过去那样私有,但这是一个完全不同的讨论) 。与是否成功无关(如果不成功,它将在 err 中设置错误状态),覆盖检查是否有另一个可用字符,如果是,则检查它是否是空格,如果不是,则std::ios_base::failbit在错误结果err

剩下的就是设置流以在 a 中使用这个特定的方面std::locale,并将新的方面挂接std::locale到流中:

std::locale loc(std::locale(), new num_get);
in.imbue(loc);
Run Code Online (Sandbox Code Playgroud)

sstd::locale及其facet 是内部引用计数的,即您不应该跟踪指向facet 的指针,也不需要保留std::locale周围的指针。imbue()如果它对创建的人来说似乎很麻烦,std::locale或者您想在任何地方使用这个修改后的逻辑,您可以设置std::locale用于初始化任何新创建的流的全局以使用自定义std::num_get<char>方面。