Лично я думаю, что это разумные вопросы, и я очень хорошо помню, что сам боролся с ними.Итак, начнем:
Где здесь моя ошибка?
Я бы не назвал это ошибкой , но вы, вероятно, хотите убедиться,Вы не должны отступать от того, что вы прочитали.То есть я бы реализовал три версии функций ввода.В зависимости от того, насколько сложным является декодирование определенного типа, я мог бы даже не делиться кодом, потому что в любом случае он может быть просто небольшим фрагментом.Если это больше, чем строка или две, вероятно, поделится кодом.То есть в вашем примере у меня был бы экстрактор для FooBar
, который по существу читает элементы Foo
или Bar
и соответственно инициализирует объекты.В качестве альтернативы я прочитал бы ведущую часть и затем вызвал бы общую реализацию, извлекающую общие данные.
Давайте сделаем это упражнение, потому что есть несколько вещей, которые могут быть осложнены.Из вашего описания формата мне не ясно, если «строка» и то, что следует за строкой, разделены, например, пробелом (пробел, табуляция и т. Д.).Если нет, вы не можете просто прочитать std::string
: поведение по умолчанию для них - читать до следующего пробела.Есть способы настроить поток, чтобы рассматривать символы как пробел (используя std::ctype<char>
), но я просто предположу, что есть место.В этом случае экстрактор для Foo
может выглядеть следующим образом (обратите внимание, что весь код полностью не проверен):
std::istream& read_data(std::istream& is, Foo& foo, std::string& s) {
Foo tmp(s);
if (is >> get_char<'('> >> tmp.m_x >> get_char<','> >> tmp.m_y >> get_char<')'>)
std::swap(tmp, foo);
return is;
}
std::istream& operator>>(std::istream& is, Foo& foo)
{
std::string s;
return read_data(is >> s, foo, s);
}
Идея состоит в том, что read_data()
читает частьFoo
, который отличается от Bar
при чтении FooBar
.Аналогичный подход будет использоваться для Bar
, но я опускаю это.Более интересным является использование этого забавного get_char()
шаблона функции.Это то, что называется манипулятором , и это просто функция, принимающая ссылку на поток в качестве аргумента и возвращающая ссылку на поток.Поскольку у нас есть разные символы, которые мы хотим прочитать и сравнить, я сделал это шаблоном, но у вас также может быть одна функция для каждого символа.Мне просто лень его печатать:
template <char Expect>
std::istream& get_char(std::istream& in) {
char c;
if (in >> c && c != 'e') {
in.set_state(std::ios_base::failbit);
}
return in;
}
В моем коде немного странно то, что проверок на работоспособность мало.Это потому, что поток просто установит std::ios_base::failbit
, когда чтение участника не удастся, и мне не нужно беспокоиться.Единственный случай, когда на самом деле добавлена специальная логика, это get_char()
, чтобы иметь дело с ожиданием определенного символа.Аналогично, пропуски пропусков символов (т. Е. Использование std::ws
) не выполняются: все входные функции являются formatted input
функциями, и по умолчанию они пропускают пробел (вы можете отключить это, используя, например, in >> std::noskipws
), но затемвещей не будет работать.
При аналогичной реализации для чтения Bar
чтение FooBar
будет выглядеть примерно так:
std::istream& operator>> (std::istream& in, FooBar& foobar) {
std::string s;
if (in >> s) {
switch ((in >> std::ws).peek()) {
case '(': { Foo foo; read_data(in, foo, s); foobar = foo; break; }
case '[': { Bar bar; read_data(in, bar, s); foobar = bar; break; }
default: in.set_state(std::ios_base::failbit);
}
}
return in;
}
Этот код использует Неформатированная функция ввода , peek()
, которая просто смотрит на следующий символ.Он либо возвращает следующий символ, либо возвращает std::char_traits<char>::eof()
в случае неудачи.Таким образом, если есть открывающая скобка или открывающая скобка, мы имеем read_data()
.В противном случае мы всегда терпим неудачу.Решил ближайшую проблему.О распространении информации ...
Должен ли кто-нибудь написать свои звонки оператору >>, чтобы оставить исходные данные все еще доступными после сбоя?
Общий ответ:нет.Если вам не удалось прочитать что-то пошло не так, и вы сдаетесь.Это может означать, что вам нужно работать больше, чтобы не потерпеть неудачу.Если вам действительно нужно отступить от позиции, в которой вы находились, для анализа ваших данных, вы можете сначала прочитать данные в std::string
, используя std::getline()
, а затем проанализировать эту строку.Использование std::getline()
предполагает наличие отдельного символа, на котором можно остановиться.По умолчанию используется перевод строки (отсюда и название), но вы также можете использовать и другие символы:
std::getline(in, str, '!');
Это остановится на следующем восклицательном знаке и сохранит все символы до него в str
. Это также извлечет символ завершения, но не сохранит его. Это иногда делает интересным, когда вы читаете последнюю строку файла, который может не иметь новой строки: std::getline()
успешно, если он может прочитать хотя бы один символ. Если вам нужно узнать, является ли последний символ в файле новой строкой, вы можете проверить, достиг ли поток:
if (std :: getline (in, str) && in.eof ()) {std :: cout << "файл не заканчивается новой строкой \"; } </p>
Если так, как я могу сделать это эффективно?
Потоки по своей природе являются однопроходными: вы получаете каждый символ только один раз, а если пропустите один, вы его потребляете. Таким образом, вы обычно хотите структурировать свои данные таким образом, чтобы вам не приходилось возвращаться назад. Тем не менее, это не всегда возможно, и у большинства потоков есть буфер под капотом, два символа которого могут быть возвращены. Поскольку потоки могут быть реализованы пользователем, нет гарантии, что символы могут быть возвращены. Даже для стандартных потоков нет никакой гарантии.
Если вы хотите вернуть символ, вы должны вернуть именно тот символ, который вы извлекли:
char c;
if (in >> c && c != 'a')
in.putback(c);
if (in >> c && c != 'b')
in.unget();
Последняя функция имеет немного лучшую производительность, потому что она не должна проверять, что персонаж действительно тот, который был извлечен. У него также меньше шансов потерпеть неудачу. Теоретически, вы можете вернуть столько символов, сколько хотите, но большинство потоков не будут поддерживать больше, чем несколько во всех случаях: если есть буфер, стандартная библиотека заботится о том, чтобы «не получать» все символы до начала буфера достигнуто Если возвращается другой символ, он вызывает виртуальную функцию std::streambuf::pbackfail()
, которая может или не может сделать больше доступного буферного пространства. В реализованных мною потоковых буферах он обычно просто не работает, т.е. я обычно не переопределяю эту функцию.
Если нет, есть ли способ «сохранить» (и восстановить) полный статус входного потока: состояние и данные?
Если вы хотите полностью восстановить состояние, в котором вы были, включая персонажей, ответ: конечно, есть. ... но нет легкий способ. Например, вы можете реализовать буфер потока фильтрации и вернуть символы обратно, как описано выше, чтобы восстановить читаемую последовательность (или поддержать поиск или явную установку метки в потоке). Для некоторых потоков вы можете использовать поиск, но не все потоки поддерживают это. Например, std::cin
обычно не поддерживает поиск.
Восстановление персонажей - это только половина истории. Другие вещи, которые вы хотите восстановить, это флаги состояния и любые данные форматирования. На самом деле, если поток перешел в сбойное или даже плохое состояние, вам нужно очистить флаги состояния, прежде чем поток выполнит большинство операций (хотя я думаю, что форматирование может быть сброшено в любом случае):
std::istream fmt(0); // doesn't have a default constructor: create an invalid stream
fmt.copyfmt(in); // safe the current format settings
// use in
in.copyfmt(fmt); // restore the original format settings
Функция copyfmt()
копирует все поля, связанные с потоком, которые связаны с форматированием. Это:
- язык
- флаги
- хранилище информации iword () и pword ()
- события потока
- исключения
- состояние потоков
Если вы не знаете о большинстве из них, не беспокойтесь: большинство вещей, о которых вы, вероятно, не заботитесь. Ну, пока вам это не нужно, но к тому времени вы, надеюсь, приобрели некоторую документацию и прочитали об этом (или спросили и получили хороший ответ).
Какая разница между сбойным битом и плохим битом? Когда мы должны использовать один или другой?
Наконец, короткий и простой:
failbit
устанавливается при обнаружении ошибок форматирования, например, ожидается число, но символ 'T' найден. badbit
устанавливается, когда что-то идет не так в инфраструктуре потока. Например, когда буфер потока не установлен (как в потоке fmt
выше), для потока установлен std::badbit
. Другая причина в том, что выдается исключение (и перехватывается с помощью маски exceptions()
; по умолчанию перехватываются все исключения).
Существует ли какая-либо онлайн-справка (или книга), которая глубоко объясняет, как бороться с iostreams? не только основные вещи: полная обработка ошибок.
Ах да, рад, что вы спросили. Вы, вероятно, хотите получить «Стандартную библиотеку C ++» Николая Йосуттиса. Я знаю, что эта книга описывает все детали, потому что я внесла свой вклад в ее написание. Если вы действительно хотите знать все о IOStreams и локалях, вам нужны Angelika Langer & Klaus Kreft "IOStreams and Locales". В случае, если вам интересно, откуда я взял информацию изначально: это были «IOStreams» Стива Тила, я не знаю, находится ли эта книга в печати, и в ней не хватает многих вещей, которые были представлены во время стандартизации. Поскольку я реализовал свою собственную версию IOStreams (и локалей), я знаю и о расширениях.