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

时间:2012-01-21 12:22:32

标签: c++ file ifstream

我的代码从文本文件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”时从此代码行ios::failure中抛出input_stream>>Row_Count;异常?

2 个答案:

答案 0 :(得分:4)

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

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

  1. 创建一个简单的操纵器,用于检查空白字符上的流。但是,这意味着您将使用in >> value >> is_space
  2. 之类的内容来阅读您的值
  3. 创建自定义std::num_get<char>构面,将其安装到std::locale,将imbue()std::locale安装到您的信息流中。它涉及的有点多,但不需要对读取整数的方式进行任何更改。
  4. 像这样创建一个操纵器是相当简单的:

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

    现在,更改数字的读取方式更有趣,我怀疑我刚刚命名了大多数人都不知道的标准库类。所以,让我们快速为此输入一个例子。我将仅更改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;
        }
    };
    

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

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

    std::locale

    std::locale loc(std::locale(), new num_get); in.imbue(loc); 及其构面在内部被引用计数,即您不应跟踪指向构面的指针,也不需要保持std::locale左右。如果std::locale创建的imbue()似乎很麻烦,或者您希望在任何地方使用此修改后的逻辑,则可以设置全局std::locale,用于将任何新创建的流初始化为使用自定义std::locale方面。

答案 1 :(得分:2)

你可以这样做:

#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是boollonglong longunsigned shortunsigned longunsigned long longfloat之一, doublelong doublevoid*