main.gif (47332 bytes)

InterReklama
InterReklama Advertising

РЕКЛАМА

6. Классы для ввода-вывода потоков.

RLE Banner Network

TBN 100x100
TBN.ru
6.1. Система классов ввода-вывода

Система ввода-вывода С++ основывается на концепции потоков данных, поток представляет собой, с одной стороны, последовательность данных, с другой стороны поток рассматривается как переменная некоторого объектного типа. Это позволяет вынести общие свойства и операции процессов ввода-вывода в определения базовых классов. Ввод-вывод из файлов и консоли, как правило, выполняется с использованием буфера и получение данных программой или вывод данных сводится к пересылке данных из одной области памяти в другую. Реальное обращение к внешним устройствам происходит только при исчерпании данных в буфере (при вводе) или при заполнении буфера (при выводе).

Система классов ввода-вывода С++ использует два базовых класса: класс ios и класс streambuf.

В классе ios определены данные, характеризующие состояние потока, и функции, позволяющие получить доступ к информации о состоянии потока или изменить его состояние. Состояние потока определяется набором битовых флагов, для обращения к отдельным флагам в классе ios описаны перечислимые константы:

- биты состояния (статуса) потока

enum io_state { goodbit = 0x00, // никакие биты не установлены, все хорошо
                     eofbit = 0x01, // конец файла
                     failbit = 0x02, // ошибка в последней операции ввода/вывода
                     badbit = 0x04, // попытка выполнить неверную операцию
                     hardfail = 0x80 // неисправимая ошибка
                   };

- биты режима использования потока (режима ввода/вывода)

enum open_mode { in = 0x01, // поток открыт для чтения
                     out = 0x02, // поток открыт для записи
                     ate = 0x04, // перейти в конец файла при открытии
                     app = 0x08, // режим добавления в конец файла
                     trunc = 0x10, // усечение существующего файла
                  nocreate = 0x20, // ошибка открытия файла, если он не существует
                  noreplace = 0x40, // ошибка открытия, если файл существует
                     binary = 0x80 // двоичный (не текстовый) файл
                   };

- флаги направления позиционирования в потоке

enum seek_dir { beg=0, cur=1, end=2 };

- флаги - манипуляторы управления вводом/выводом

enum { skipws = 0x0001, // пропускать пробелы при вводе
              left = 0x0002, // выравнивание влево при выводе
             right = 0x0004, // выравнивание вправо при выводе
          internal = 0x0008, // пробел после знака или основания системы счисления
               dec = 0x0010, // преобразование в десятичную систему счисления
               oct = 0x0020, // преобразование в восьмеричную систему счисления
               hex = 0x0040, // шестнадцатеричное преобразование
          showbase = 0x0080, // использовать индикатор системы счисления при выводе
         showpoint = 0x0100, // указывать десятичную точку при выводе
                                //(в числах с плавающей точкой)
         uppercase = 0x0200, // прописные буквы при шестнадцатеричном выводе
           showpos = 0x0400, // добавлять '+' для положительных целых
         scientific= 0x0800, // применять нотацию вида 1.2345E2
             fixed = 0x1000, // применять нотацию вида 123.45
           unitbuf = 0x2000, // очищать все потоки после вставки в поток
             stdio = 0x4000, // очищать stdout, stderr после вставки в поток
         boolalpha = 0x8000  // вставлять/извлекать булевы как текст или цифры
          };

Поскольку эти перечислимые константы объявлены как компоненты класса ios, для доступа к ним требуется уточнение контекста, например, ios::in.

Класс streambuf обеспечивает создание и использование буфера ввода-вывода и содержит компоненты-данные для управления буфером и методы доступа к данным в буфере. Объект класса ios содержит указатель на связанный с ним объект streambuf.

Классы istream и ostream являются производными от класса ios, в них определены функции, выполняющие ввод (istream) и вывод (ostream) данных базовых типов и строк.

В классе istream определены операции бесформатного ввода (без преобразования вводимых данных)и операции форматного ввода с преобразованием из внешнего представления во внутреннее.

Функции get() читают из потока данные типа char в массив, определяемый первым параметром, второй параметр задает максимальное число вводимых символов (l), третий параметр устанавливает символ-ограничитель, ограничивающий ввод. За последним введенным символом в массив пишется символ ‘\0’.

istream _FAR & _RTLENTRY get( char _FAR *, int l, char = '\n');
istream _FAR & _RTLENTRY get( signed char _FAR *, int l, char = '\n');
istream _FAR & _RTLENTRY get( unsigned char _FAR *, int l, char ='\n');

Функции read() выполняют чтение из потока и занесение в массив указанного числа символов

istream _FAR & _RTLENTRY read( char _FAR *, int l);
istream _FAR & _RTLENTRY read( signed char _FAR *, int l);
istream _FAR & _RTLENTRY read(unsigned char _FAR *, int l);

Для ввода строки, заканчивающейся символом-ограничителем, служит функция getline() при этом символ-ограничитель также заносится в массив

istream _FAR & _RTLENTRY getline( char _FAR *, int, char = '\n');
istream _FAR & _RTLENTRY getline( signed char _FAR *, int, char= '\n');
istream _FAR & _RTLENTRY getline(unsigned char _FAR *, int, char= '\n');

Для извлечения из потока данных типа char вплоть до символа-ограничителя служит вариант функции get():

istream _FAR & _RTLENTRY get(streambuf _FAR &, char = '\n');

Другие варианты функции get() обеспечивают извлечение одного символа

istream _FAR & _RTLENTRY get( char _FAR &);
istream _FAR & _RTLENTRY get( signed char _FAR &);
istream _FAR & _RTLENTRY get(unsigned char _FAR &);
int _RTLENTRY get(); // возвращает значение символа
int _RTLENTRY peek(); // возвращает следующий символ без удаления его из потока
int _RTLENTRY gcount(); // число символов, извлеченных из потока
// возвращение указанного символа в поток ввода
istream _FAR & _RTLENTRY putback(char);

Пропуск символов с остановкой по ограничителю выполняет функция

istream _FAR & _RTLENTRY ignore(int = 1, int = EOF);

Форматный ввод релизуется на основе переопределения операций ввода

istream _FAR & _RTLENTRY operator>> (bool _FAR &);
istream _FAR & _RTLENTRY operator>>
                      (istream _FAR & (_RTLENTRY *_f)(istream _FAR &));
istream _FAR & _RTLENTRY operator>>
                      (ios _FAR & (_RTLENTRY *_f)(ios _FAR &) );
istream _FAR & _RTLENTRY operator>> ( char _FAR *);
istream _FAR & _RTLENTRY operator>> ( signed char _FAR *);
istream _FAR & _RTLENTRY operator>> (unsigned char _FAR *);
istream _FAR & _RTLENTRY operator>> ( char _FAR &);
istream _FAR & _RTLENTRY operator>> ( signed char _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned char _FAR &);
istream _FAR & _RTLENTRY operator>> (short _FAR &);
istream _FAR & _RTLENTRY operator>> (int _FAR &);
istream _FAR & _RTLENTRY operator>> (long _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned short _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned int _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned long _FAR &);
istream _FAR & _RTLENTRY operator>> (float _FAR &);
istream _FAR & _RTLENTRY operator>> (double _FAR &);
istream _FAR & _RTLENTRY operator>> (long double _FAR &);

Извлечение из istream и вставка в объект типа streambuf выполняет оператор-функция

istream _FAR & _RTLENTRY operator>> (streambuf _FAR *);

Макрос FAR управляет способом представления указателей, макрос RTLENTRY определяет применение стандартной билиотеки времени выполнения.

Аналогичные операции для вывода определены в классе ostream:

// вставить символ в поток
ostream _FAR & _RTLENTRY put( char);
ostream _FAR & _RTLENTRY put(signed char);
ostream _FAR & _RTLENTRY put(unsigned char);
// вставить в поток строку
ostream _FAR & _RTLENTRY write(const char _FAR *, int l);
ostream _FAR & _RTLENTRY write(const signed char _FAR *, int l);
ostream _FAR & _RTLENTRY write(const unsigned char _FAR *, int l);

Операции форматированного вывола

Вывод "true" или "false" для данных типа bool

ostream _FAR & _RTLENTRY ostream::operator<< (bool);

Вывод ланных типа char

ostream _FAR & _RTLENTRY operator<< ( char);
ostream _FAR & _RTLENTRY operator<< ( signed char);
ostream _FAR & _RTLENTRY operator<< (unsigned char);

Вывод числовых данных с преобразованием во внешнее представление

ostream _FAR & _RTLENTRY operator<< (short);
ostream _FAR & _RTLENTRY operator<< (unsigned short);
ostream _FAR & _RTLENTRY operator<< (int);
ostream _FAR & _RTLENTRY operator<< (unsigned int);
ostream _FAR & _RTLENTRY operator<< (long);
ostream _FAR & _RTLENTRY operator<< (unsigned long);
ostream _FAR & _RTLENTRY operator<< (float);
ostream _FAR & _RTLENTRY operator<< (double);
ostream _FAR & _RTLENTRY operator<< (long double);

Вывод строк, оканчивающихся нулевым байтом

ostream _FAR & _RTLENTRY operator<< (const char _FAR *);
ostream _FAR & _RTLENTRY operator<< (const signed char _FAR*);
ostream _FAR & _RTLENTRY operator<< (const unsigned char _FAR*);

Вывод значения указателя в символьном формате

ostream _FAR & _RTLENTRY operator<< (void _FAR *);

Извлечение данных их объекта streambuf и вставка в тот же ostream

ostream _FAR & _RTLENTRY operator<< (streambuf _FAR *);

Вывод значений манипуляторов

ostream _FAR & _RTLENTRY operator<<
             (ostream _FAR & (_RTLENTRY *_f)(ostream _FAR &));
ostream _FAR & _RTLENTRY operator<<
                 (ios _FAR & (_RTLENTRY *_f)(ios _FAR &));

Имеется также класс iostream, производный от класса ios и объединяющий возможности классов istream и ostream.

Для рассмотренных выше классов отсутствуют конструкторы копирования и операция присваивания, точнее, они объявлены, но не определены. Для тех случаев, когда конструктор копирования и операция присваивания необходимы, предусмотрены классы istream_withassign, ostream_withassign и iostream_withassign. Как экземпляры объектов этих классов всегда объявляется объект cin (экземпляр istream_withassign), обычно предназначенный для ввода с клавиатуры, и объекты cout, cerr и clog (экземпляры ostream_withassign), обычно предназначенные для вывода на экран.

Ввод-вывод для дисковых файлов обепечивается классами, описания которых содержатся в файле fstream.h.

Класс filebuf, производный от streambuf, предназначен для добавления в streambuf дополнительных средств управления буфером ввода-вывода.

Класс fstreambase, производный от класса ios, служит базой для остальных классов, обеспечивающих файловый ввод-вывод, в нем определены методы:

void _RTLENTRY open(const char _FAR *, int, int = filebuf::openprot);
void _RTLENTRY attach(int);
void _RTLENTRY close();
void _RTLENTRY setbuf(char _FAR *, int);

Назначение этих методов очевидным образом следует из их названий.

Для непосредственной работы с файлами служат классы ifstream, ofstream и fstream, базой для них служат классы fstreambase и, соответственно, istream, ostream и iostream.

6.2. Вывод в файл. Ввод из файла

Для вывода данных в дисковый файл в программе должна присутствовать директива препроцессора

#include <fstream.h>

подключающая описания необходимых классов.

Прежде чем выводить данные необходимо создать объект типа ofstream, для которого имеется несколько конструкторов:

_RTLENTRY ofstream(); // Пустой объект, без привязки к файлу
// С привязкой к файлу, полное имя которого задается первым аргументом:
_RTLENTRY ofstream(const char _FAR *, int = ios::out,
                        int = filebuf::openprot);
// С привязкой к ранее открытому файлу, заданному своим дескриптором
_RTLENTRY ofstream(int);
// То же, что и предыдущий вариант, но задается новый буфер вывода
_RTLENTRY ofstream(int __f, char _FAR *, int);

Наиболее часто оказывается полезным второй вариант конструктора, в котором указывается только первый параметр - полное имя файла. Этот конструктор создает объект типа ofstream, открывает указанный файл и присоединяет его к потоку вывода.

Собственно операции вывода реализуются вызовом методов put, write или с использованием переопределенных операций <<.

Аналогичным способом обеспечивается и ввод из файла: создается объект типа ifstream и для ввода применяются методы get, read или переопределенные операции >>.

Для типа ifstream имеется набор аналогичных конструкторов:

_RTLENTRY ifstream();
_RTLENTRY ifstream(const char _FAR *,int = ios::in,
                     int = filebuf::openprot);
_RTLENTRY ifstream(int);
_RTLENTRY ifstream(int __f, char _FAR *, int);

В качестве примера рассмотрим программу, копирующую данные из одного файла в другой.

#include <fstream.h>
#include <process.h> // Для вызова exit
int main ( int argc, char* argv [ ] )
     { char ch;
       if ( argc != 3 ) // Проверка числа аргументов
         { cerr << “ Вызов dcopy файл1 файл2 \n” ; exit ( 1 ) ; }
       ifstream source( argv [ 1 ] ) ; // Входной поток
       if ( ! source )
         { cerr << “ Нельзя открыть входной файл “ << argv [ 1 ] ;
                exit ( 1 ); }
       ofstream dest ( argv [2 ] ) ;
       if ( ! dest )
         { cerr << “ Нельзя открыть выходной файл “ << argv [ 2 ] ;
               exit ( 1 ); }
       while ((ch = source.get ( ) ) != EOF )
                dest.put( ch );
       close ( source ); close ( dest );
       return 0 ;
      }

6.3. Ввод-вывод данных объектных типов

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

Для вывода объекта в файл в определение класса может быть включена функция-компонента с параметром ссылкой на объект типа ostream. Часто такой функции назначают имя print или printon. Более изящным считается переопределение оператора << для вывода объектного данного. Если компоненты-данные объекта имеют уровень доступа protected или private, а оператор << не является компонентой класса, его следует объявить как friend-метод.

Пусть, например, в программе определен класс complex:

class complex
   { double re, im ;
     public:
     complex (double r =0, double i =0 ): re ( r ), im ( i )
              { } // конструктор
     double real ( ) {return ( re ); }
     double image ( ) { return ( im ); }
     /* другие методы */
   }

Тогда для форматированного вывода комплексного числа в формате ( вещественная часть, мнимая часть ) можно так переопределить операцию << :

ostream& operator << ( ostream& s, complex c )
   { int old_precision = s.precision ( 4 ); // установка числа дробных цифр
       s << “(“ << c.real( ) <<
            “, “ << c.image( ) << “)” ;
       s.precision ( old_precision ) ; // восстановление числа дробных цифр
       return ( s );
    }

В данном случае в переопределении << нет обращения к личным переменным класса Complex. Если не использовать методы real и image, переопределение << нужно было бы включить в описание класса Complex с описателем friend.

Ввод объектного данного из файла или стандартного потока организуется аналогично: либо в определение класса включается функция-компонента для инициализации компонент данных их входного потока, либо переопределяется операция >> для ввода компонент-данных из потока. В некоторых случаях оказывается более удобным включить в описание класса дополнительный конструктор с параметром-ссылкой на объект типа istream, при этом все базовые классы должны иметь аналогичные конструкторы.

Пусть в описание класса Complex включена переопределенная операция >>:

class complex

{ double re, im ;
       public:
       complex (double r =0,double i=0 ): re( r ), im( i ) { } // конструктор
       double real ( ) {return ( re ); }
       double image ( ) { return ( im ); }
   friend istream& operator >> ( istream& , Complex& );
      /* другие методы */
      }

Если предположить, что комплексные числа поступают из потока либо в виде вещественного числа, возможно заключенного в скобки, либо в виде пары чисел, разделенных запятой и заключенной в скобки, то переопределяемую операцию ввода можно описать следующим образом:

istream& operator >> ( istream& s, Complex& c )
    { double re_n = 0, im_n = 0;
       char ch = 0;
          s >> ch ;
       if ( ch == ‘(‘ )
          { s >> re_n >> ch ;
            if ( ch == ‘,’ ) s >> im_n >> ch;
            if ( ch != ‘)’) s.clear ( ios::failbit ); //Установка признака ошибки
           }
      else { s.putback ( ch ); s >> re_n ; }
      if ( s ) { c.re = re_n; c.im = im_n; } // Если не было ошибки
      return ( s );

}

Рассмотренные выше примеры сохранения объектов в потоке и восстановления их из потока иллюстрируют наиболее простые варианты этих операций. Проблема сохранения объектов в потоке существенно усложняется, когда требуется сохранять в одном потоке объекты разных типов, когда между объектами разных типов имеются ссылки и некоторый объект содержит указатель на объект другого типа, причем на один и тот же объект могут ссылаться несколько других объектов. В этой ситуации требуется для каждого сохраняемого в потоке объекта заносить в поток идентификатор типа объекта, гарантировать, что каждый объект, на который имеются ссылки из других объектов, будет помещен в поток только один раз. Средства для разрешения этих проблем имеются в библиотеке классов-контейнеров classlib, содержащей файл objstrm.h с определениями необходимых классов и макросов.

back.gif (1087 bytes)ind.gif (1892 bytes)next.gif (1100 bytes)
Hosted by uCoz