Как правильно читать беззнаковые переменные int из файла, используя ifstream? - PullRequest
7 голосов
/ 21 января 2012

Мой код читает беззнаковые переменные типа 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;
}

Работает отлично.Но когда я заполняю текстовый файл неправильными данными

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».Это так?Как установить символ пробела в качестве разделителя для выброса исключения ios::failure из этой строки кода input_stream>>Row_Count; при чтении «33abcd4»?

Ответы [ 2 ]

4 голосов
/ 21 января 2012

Нормальное извлечение целочисленного значения будет успешным, если поток сможет прочитать любое целочисленное значение.То есть, если есть хотя бы одна цифра, необязательно, за которой следует что-либо, чтение целого числа завершается успешно.Обычные операции извлечения не пытаются читать больше, в частности, они не пытаются найти следующий пробел.

Судя по звукам, вы хотите быть уверены, чтоэто пробел после вашего номера и сбой, если его нет.Я могу думать о двух разных подходах, чтобы сделать это:

  1. Создать простой манипулятор, который проверяет, находится ли поток на пробеле.Это, однако, означает, что вы будете читать ваши значения, используя что-то вроде in >> value >> is_space.
  2. Создайте пользовательский std::num_get<char> фасет, установите его в std::locale и imbue() this 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<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), переопределение проверяет, есть ли другой доступный символ, и, если так, проверяет, является ли это пробелом, и если нет, устанавливает std::ios_base::failbit в результате ошибки err.

Осталось настроить поток на использование этого конкретного фасета в std::locale и подключить новый std::locale к потоку:

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

std::locale s и его фасеты подсчитываются внутренними ссылками, т. Е. Вы не должны отслеживать указатель на фасет, и вам не нужно также хранить std::locale.Если вам кажется неудобным imbue() созданный std::locale или вы хотите использовать эту измененную логику везде, вы можете установить глобальный std::locale, который используется для инициализации любого вновь созданного потока, для использования пользовательского std::num_get<char>фаска.

2 голосов
/ 21 января 2012

Вы можете сделать это так:

#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 является одним из bool, long, long long, unsigned short, unsigned long, unsigned long long, float, double, long double и void*.

...