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 с определениями
необходимых классов и макросов. |