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

发布于 2024-12-28 03:07:14 字数 1349 浏览 6 评论 0 原文

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

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;
}

效果非常好。 但是,当我用错误的数据填充文本文件时,

33abcd4  567fg8

它的工作方式如下:

input_stream>>Row_Count; //Row_Count = 33;
input_stream>>Column_Count; // throws an ios::failure exception

为什么此行 input_stream>>Row_Count; 不抛出异常? 据我了解, input_stream 将任何非数字符号视为分隔符,下一步它会尝试读取“abcd”。是这样吗? 如何设置空格符号作为分隔符,以便在读取“33abcd4”时从这行代码 input_stream>>Row_Count; 引发 ios::failure 异常?

My code reads unsigned int variables from the text file 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;
}

It works excellent.
But when I fill text file with a wrong data

33abcd4  567fg8

It works in such way:

input_stream>>Row_Count; //Row_Count = 33;
input_stream>>Column_Count; // throws an ios::failure exception

Why doesn't this line input_stream>>Row_Count; throw exception?
As I understood, input_stream considers any non-numeric symbol as delimiter, and on the next step it tries to read "abcd". Is it so?
How to set a Space-symbol as delimiter to throw an ios::failure exception from this line of code input_stream>>Row_Count; while reading "33abcd4"?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

抚你发端 2025-01-04 03:07:14

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

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

  1. 创建一个简单的操纵器,用于检查流是否位于空白字符上。然而,这意味着您将使用 in >> 之类的方式读取您的值。值>> is_space
  2. 创建自定义 std::num_get 构面,将其安装到 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;
}

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

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;
    }
};

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

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

std::locale loc(std::locale(), new num_get);
in.imbue(loc);

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

The normal extraction of an integer value succeeds if the stream could read any integer value. That is, if there is at least one digit optionally followed by anything the read of an integer succeeds. The normal extraction operations don't try to read more, in particular they don't try to find the next whitespace.

From the sounds of it, you want to be sure that there is a whitespace following your number and fail if there is not. I can think of two different approaches to do this:

  1. Create a simple manipulator which checks for the stream being on a whitespace character. This, however, means that you would read your values using something like in >> value >> is_space.
  2. Create a custom std::num_get<char> facet, install it into a std::locale, and imbue() this std::locale into your stream(s). It is a bit more involved but doesn't require any changes to the way integers are read.

Creating a manipulator like this is fairly trivial:

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

Now, changing the way numbers are read is more interesting and I suspect I had just named a number of standard library classes most people are fairly unaware of. So, let's quickly type out an example for this as well. I will change the std::num_get<char> facet only to deal with unsigned int: to do it for other integral types it is necessary to override more functions. So, here is a replacement for the std::num_get<char> facet:

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;
    }
};

All this does is to derive a class from std::num_get<char> and override one of its virtual functions. The implementation of this function is fairly straight forward: start with reading the value by delegating to the base class (I just realized that virtual functions indeed want to protected rather than private as I have though in the past but this is an entirely different discussion). Independent of whether this was successful (if it was not, it will have set up an error state in err) the override checks if there is another character available and, if so, checks if it is a space and if not sets a std::ios_base::failbit in the error result err.

What remains is to set up the stream to use this particular facet in a std::locale and hook the new std::locale into a stream:

std::locale loc(std::locale(), new num_get);
in.imbue(loc);

The std::locales and its facets are internally reference counted, i.e. you shouldn't keep track of the pointer to the facet and you don't need to keep the std::locale around either. If it seems to be to cumbersome to imbue() the created std::locale or you want to use this modified logic everywhere, you can set the global std::locale which is used to initialize any newly created stream to use the custom std::num_get<char> facet.

春花秋月 2025-01-04 03:07:14

您可以这样做:

#include <iostream>
#include <locale>

class my_num_get : public std::num_get<char> {
protected:
    iter_type do_get(iter_type in, iter_type end, std::ios_base& str, std::ios_base::iostate& err, unsigned int& v) const
    {
        in = std::num_get<char>::do_get(in, end, str, err, v);
        if(in != end && !std::isspace(*in, str.getloc()))
            err |= std::ios_base::failbit;
        return in;
    }
};

int main() {
    using namespace std;
    cin.imbue(std::locale(cin.getloc(), new my_num_get));
    cin.exceptions(ios_base::badbit | ios_base::failbit);
    try {
        unsigned int x;
        cin >> x;
    } catch(const std::exception &e) {
        cerr << e.what() << "\n";
    }
}

如果您希望这也适用于其他类型,请以相同的方式实现以下内容:

iter_type do_get(iter_type, iter_type, ios_base&, ios_base::iostate&, T& v) const

其中 T 是 boollong 之一, 长长无符号短无符号长无符号长长浮点doublelong double无效*

You can do it this way:

#include <iostream>
#include <locale>

class my_num_get : public std::num_get<char> {
protected:
    iter_type do_get(iter_type in, iter_type end, std::ios_base& str, std::ios_base::iostate& err, unsigned int& v) const
    {
        in = std::num_get<char>::do_get(in, end, str, err, v);
        if(in != end && !std::isspace(*in, str.getloc()))
            err |= std::ios_base::failbit;
        return in;
    }
};

int main() {
    using namespace std;
    cin.imbue(std::locale(cin.getloc(), new my_num_get));
    cin.exceptions(ios_base::badbit | ios_base::failbit);
    try {
        unsigned int x;
        cin >> x;
    } catch(const std::exception &e) {
        cerr << e.what() << "\n";
    }
}

If you want this to work for other types too, then implement the following in the same way:

iter_type do_get(iter_type, iter_type, ios_base&, ios_base::iostate&, T& v) const

where T is one of bool, long, long long, unsigned short, unsigned long, unsigned long long, float, double, long double and void*.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文