Шаг 521.
Библиотека STL. Ввод-вывод с использованием потоковых классов. Классы потоковых буферов. Пользовательские буферы ввода (окончание)

    На этом шаге мы рассмотрим пример использования пользовательских буферов ввода.

    Следующий пример показывает, как может выглядеть подобная реализация. Класс inbuf реализует буфер ввода, рассчитанный на десять символов. Буфер условно делится на две части: "область возврата" из четырех символов и "нормальный" буфер ввода из шести символов.

#include <cstdio>
#include <cstring>
#include <streambuf>

extern "C" {
    int read (int fd, char* buf, int num);
}

class inbuf : public std::streambuf {
  protected:
    // Буфер данных:
    //  - до четырех символов в области возврата,
    //  - до шести символов в обычном буфере ввода.
    static const int bufferSize = 10;    // Размер буфера данных
    char buffer[bufferSize];             // Буфер

  public:
    // Конструктор
    //  - Инициализация пустого буфера
    //  - без области возврата
    //  => принудительный вызов underflow()
    inbuf() {
        setg (buffer+4,     // Начало области возврата
              buffer+4,     // Текущая позиция
              buffer+4);    // Конечная позиция
    }

  protected:
    // Вставка новых символов в буфер
    virtual int_type underflow () {

        // Текущая позиция чтения предшествует концу буфера?
        if (gptr() < egptr()) {
            return traits_type::to_int_type(*gptr());
        }

        // Обработка размера области возврата
        //  - использовать количество прочитанных символов,
        //  - но не более 4
        int numPutback;
        numPutback = gptr() - eback();
        if (numPutback > 4) {
            numPutback = 4;
        }

        // Копирование до четырех ранее прочитанных символов
        // в область возврата (первые четыре символа)
        std::memmove (buffer+(4-numPutback), gptr()-numPutback,
                      numPutback);

        // Чтение новых символов
        int num;
        num = read (0, buffer+4, bufferSize-4);
        if (num <= 0) {
            // ОШИБКА или EOF
            return EOF;
        }

        // Сброс указателей
        setg (buffer+(4-numPutback),   // Начало области возврата
              buffer+4,                // Текущая позиция чтения
              buffer+4+num);           // Конец буфера

        // Вернуть следующий символ
        return traits_type::to_int_type(*gptr());
    }
};

    Конструктор инициализирует все указатели так, что буфер остается абсолютно пустым (рисунок 1).


Рис.1. Буфер ввода после инициализации

    При попытке прочитать символы из этого буфера вызывается функция underflow(), всегда используемая потоковыми буферами для чтения следующих символов. Сначала функция проверяет наличие прочитанных символов в буфере ввода. Если такие символы есть, они перемещаются в область возврата функцией memcpy(). В буфере ввода хранятся не более четырех последних символов. Низкоуровневая функция ввода-вывода POSIX read() читает следующий символ из стандартного канала ввода. После того как указатели буфера будут настроены в соответствии с изменившейся ситуацией, возвращается первый прочитанный символ.

    Предположим, при первом вызове read() были прочитаны символы Н, а, l, l, о, w. Буфер ввода переходит в состояние, изображенное на рисунке 2.


Рис.2. Буфер ввода после чтения символов Hallow

    Область возврата пуста, потому что буфер был заполнен в первый раз, и готовых к возврату символов еще нет.

    После извлечения этих символов последние четыре символа перемещаются в область возврата, после чего читаются новые символы. Допустим, при следующем вызове read() были прочитаны символы е, е, n, \n (рисунок 3).


Рис.3. Буфер ввода после чтения еще четырех символов

    Пример использования этого потокового буфера:

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include "inbuf1.hpp"

#include <conio.h>   //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;

std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

int main (int argc, char* argv[])
{
  inbuf ib;                // Создание специального потокового буфера
  std::istream in(&ib);    // Инициализация выходного потока этим буфером

  char c;
  for (int i=1; i<=20; i++) {
      // Чтение следующего символа (из буфера)
      in.get(c);

      // Вывод символа и очистка буфера
      std::cout << c << std::flush;

      // После вывода восьми символов
      // вернуть два последних символа в поток
      if (i == 8) {
          in.unget();
          in.unget();
      }
  }
  std::cout << std::endl;


  getch();
  return 0;
}

//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

    Результат работы приложения выглядит так:


Рис.4. Результат работы приложения

    Программа в цикле читает символы и выводит их в поток данных cout. После чтения восьмого символа выполняется возврат на два символа, в результате чего седьмой и восьмой символы выводятся дважды.

    Со следующего шага мы начнем рассматривать проблемы эффективности.




Предыдущий шаг Содержание Следующий шаг