Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 120 121 122 123 124 125 126 127 128 ... 337
одно целое, преобразовать символы-разделители в пробелы, а затем использовать поток istringstream для форматирования. Кроме обработки разделителей, заданных пользователем, в классе Punct_stream есть аналогичная возможность: если вызвать функцию case_sensitive(), то она преобразует ввод, чувствительный к регистру, в нечувствительный.

Например, можно приказать объекту класса Punct_stream прочитать строку

Man bites dog!

как

man

bites

dog

Конструктор класса Punct_stream получает поток istream, используемый как источник символов, и присваивает ему локальное имя source. Кроме того, конструктор по умолчанию делает поток чувствительным к регистру, как обычно. Можно создать объект класса Punct_stream, считывающий данные из потока cin, рассматривающий точку с запятой, двоеточие и точку как разделители, а также переводящий все символы в нижний регистр.

Punct_stream ps(cin);     // объект ps считывает данные из потока cin

ps.whitespace(";:.");     // точка с запятой, двоеточие и точка

                          // также являются разделителями

ps.case_sensitive(false); // нечувствительный к регистру

Очевидно, что наиболее интересной операцией является оператор ввода >>. Он также является самым сложным для определения. Наша общая стратегия состоит в том, чтобы считать всю строку из потока istream в строку line. Затем мы превратим все наши разделители в пробелы (' '). После этого отправим строку в поток istringstream с именем buffer. Теперь для считывания данных из потока buffer можно использовать обычные разделители и оператор >>. Код будет выглядеть немного сложнее, поскольку мы только пытаемся считать данные из потока buffer и заполняем его, только если он пуст.

Punct_stream& Punct_stream::operator>>(string& s)

{

  while (!(buffer>>s)) { // попытка прочитать данные

                         // из потока buffer

  if (buffer.bad() || !source.good()) return *this;

  buffer.clear();

  string line;

  getline(source,line); // считываем строку line

                        // из потока source

                        // при необходимости заменяем символы

  for (int i =0; i<line.size(); ++i)

    if (is_whitespace(line[i]))

      line[i]= ' ';               // в пробел

    else if (!sensitive)

      line[i] = tolower(line[i]); // в нижний регистр

    buffer.str(line);             // записываем строку в поток

  }

  return *this;

}

Рассмотрим этот код шаг за шагом. Сначала обратим внимание не нечто необычное.

while (!(buffer>>s)) {

Если в потоке buffer класса istringstream есть символы, то выполняется инструкция buffer>>s и объект s получит слово, разделенное разделителями; больше эта инструкция ничего не делает. Эта инструкция будет выполняться, пока в объекте buffer есть символы для ввода. Однако, когда инструкция buffer>>s не сможет выполнить свою работу, т.е. если выполняется условие !(buffer>>s), мы должны наполнить объект buffer символами из потока source. Обратите внимание на то, что инструкция buffer>>s выполняется в цикле; после попытки заполнить объект buffer мы должны снова попытаться выполнить ввод.

while (!(buffer>>s)) { // попытка прочитать символы из буфера

  if (buffer.bad() || !source.good()) return *this;

  buffer.clear();

  // заполняем объект buffer

}

Если объект buffer находится в состоянии bad() или существуют проблемы с источником данных, работа прекращается; в противном случае объект buffer очищается и выполняется новая попытка. Мы должны очистить объект buffer, потому что попадем в “цикл заполнения”, только если попытка ввода закончится неудачей. Обычно это происходит, если вызывается функция eof() для объекта buffer; иначе говоря, когда в объекте buffer не остается больше символов для чтения. Обработка состояний потока всегда запутанна и часто является причиной очень тонких ошибок, требующих утомительной отладки. К счастью, остаток цикла заполнения вполне очевиден.

string line;

getline(source,line); // вводим строку line из потока source

                      // при необходимости выполняем замену символов

for (int i =0; i<line.size(); ++i)

  if (is_whitespace(line[i]))

    line[i]= ' ';               // в пробел

  else if (!sensitive)

    line[i] = tolower(line[i]); // в нижний регистр

  buffer.str(line);             // вводим строку в поток

Считываем строку в объект buffer, затем просматриваем каждый символ строки в поисках кандидатов на замену. Функция is_whitespace() является членом класса Punct_stream, который мы определим позднее. Функция tolower() — это стандартная библиотечная функция, выполняющая очевидное задание, например превращает символ A в символ a (см. раздел 11.6).

После правильной обработки строки line ее необходимо записать в поток istringstream. Эту задачу выполняет функция buffer.str(line); эту команду можно прочитать так: “Поместить строку из объекта buffer класса istringstream в объект line”.

Обратите внимание на то, что мы “забыли” проверить состояние объекта source после чтения данных с помощью функции getline(). Это не обязательно, поскольку в начале цикла выполняется проверка условия !source.good().

Как всегда, оператор >> возвращает ссылку на поток *this (раздел 17.10).

Проверка разделителей проста; мы сравниваем символ с каждым символом из строки, в которой записаны разделители.

bool Punct_stream::is_whitespace(char c)

{

  for (int i = 0; i<white.size(); ++i)

    if (c==white[i]) return true;

  return false;

}

Напомним, что поток istringstream обрабатывает обычные разделители (например, символы перехода на новую строку или пробел) по-прежнему, поэтому никаких особых действий предпринимать не надо.

Осталась одна загадочная функция.

Punct_stream::operator bool()

{

  return !(source.fail() || source.bad()) && source.good();

}

Обычное использование потока istream сводится к проверке результата оператора >>. Рассмотрим пример.

while (ps>>s) { /* ... */ }

Это значит, что нам нужен способ для проверки результата выполнения инструкции ps>>s, представленного в виде булевого значения. Результатом инструкции ps>>s является объект класса Punct_stream, поэтому нам нужен способ неявного преобразования класса Punct_stream в тип bool. Эту задачу решает функция operator bool() в классе Punct_stream.

Функция-член operator bool() определяет преобразование класса Punct_stream в тип bool. В частности, она возвращает значение true, если эта операция над классом Punct_stream прошла успешно.

Теперь можем написать программу.

int main()

 // вводит текст и создает упорядоченный список всех слов

 // из

1 ... 120 121 122 123 124 125 126 127 128 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий