22 мая 2007 г.

Библиотека iostream

Как и в старичке C, в C++ нет встроенных средств для ввода и вывода данных куда бы то ни было. Редкие в наше время программисты на C++ под MS-DOS, да и вообще старшее поколение программистов, могут возразить – мол, ввод и вывод суть чтение и запись по определенным адресам в оперативной памяти (угу, а программирование суть написание машинных команд). Однако это мало того что неудобно, так еще и обладает такими нехорошими свойствами, как абсолютная платформенная непереносимость, а также несовместимость с объектно-ориентированной парадигмой. Короче говоря, по абсолютным адресам в памяти мы писать не хотим, и волшебные слова «контроллер», «DMA» и «драйвер» оставим тем, кому это интересно.
К счастью, добрые люди присобачили к C++ стандартную библиотеку, выдержанную в традициях ООП и позволяющую все, что только душа пожелает (ну, почти).
В этой и нескольких следующих статьях я собираюсь дать небольшое обзорное описание библиотеки iostream, но несколько нетрадиционное. Обычно авторы книг по C++ начинают с элементарных
cout << “Hello, World!” << endl;

и только потом описывают манипуляторы, работу с файлами и т.д. Прилежные ученики послушно переписывают примеры из книги, убеждаются, что они работают, и запоминают на всю жизнь, что cout<< – это такая хитрая языковая конструкция, позволяющая выводить на экран строки, и совершенно не связанная с файловым вводом-выводом. В головах возникает каша, которую потом долго приходится разгребать (зато сколько будет приятных сюрпризов!). Я пойду по несколько другому пути – начну с самых основ и, продвигаясь наверх, в сторону конкретизации абстрактных понятий, покажу, как там все на самом деле работает. Начнем с того, из чего состоит библиотека iostream.
Каждый уважающий себя компилятор C++ поставляется со стандартной библиотечкой ввода-вывода по имени iostream. Ключевым понятием в этой библиотеке является поток (stream) – не путать с потоком управления (thread)! Не вдаваясь в детали реализации, можно сказать, что поток – это объект, который может служить источником или приемником байтов (или и тем, и другим сразу). Не все сразу могут понять отличие потоковых средств ввода-вывода от традиционных read/write. Суть этих отличий в том, что поток – это более высокая ступень абстракции; он может использоваться как, например, для чтения из файла (и при этом он ведет себя почти как обычный read), так и для чтения данных из последовательного порта или с клавиатуры. В последнем случае данные уже имеют ярко выраженную потоковую природу.
Кстати, надо заметить, что консерваторы по-прежнему могут использовать привычную, аки домашние тапочки, библиотеку stdio, но в таком получается меньше контроля над потоками, меньше удобства и меньше соответствия парадигме ООП.
Для начала посмотрим, какие в библиотеке iostream есть заголовочные файлы:
  • fstream – классы, инкапсулирующие ввод-вывод на внешних файлах.
  • ios – базовые классы iostream; обычно этот заголовочный файл не нужно включать напрямую, он включается в других заголовочниках.
  • iostream – определяет объекты ввода-вывода на стандартных потоках.
  • ostream – определяет шаблонный базовый класс basic_ostream, управляющий выводом в потоки; этот файл также обычно не включается в программу напрямую.
  • streambuf – определяет шаблонный базовый класс basic_streambuf, управляющий буферизацией; не нужно включать напрямую.
  • iomanip – определяет ряд манипуляторов (про них ниже).
  • iosfwd – объявляет (но не определяет!) кучу шаблонных классов и typedef’ов.
  • istream – брат-близнец ostream, только в отношении потокового ввода.
  • sstream – определяет ряд классов для потоковой работы с хранящимися в памяти последовательностями.
  • strstream – определяет ряд классов для потоковой работы с char-строками.
Как нетрудно догадаться, большую часть этих заголовочников нормальный программист никогда в своей жизни не увидит (и хорошо). Обычно используются только fstream, iostream, iomanip, sstream, strstream и соответствующие им классы.
Теперь рассмотрим структуру и использование одной из частей библиотеки iostream, а именно классы для потокового ввода-вывода на файлах. Работа с ними ничуть не отличается от потокового ввода-вывода на памяти, на строках или на стандартных потоках (то есть консоли и клавиатуре).
Заголовочный файл fstream определяет ряд классов: filebuf, wfilebuf, fstream, wfstream, ifstream, wifstream, ofstream, wofstream, basic_filebuf, basic_fstream, basic_ifstream, basic_ofstream. На самом деле из приведенного списка только последние четыре пункта – классы. Они определяются следующим образом:
template <class Elem, class Tr = char_traits<Elem> >
class basic_filebuf : public basic_streambuf<Elem, Tr>
template <class Elem, class Tr = char_traits<Elem> >
class basic_fstream : public basic_iostream<Elem, Tr>
template <class Elem, class Tr = char_traits<Elem> >
class basic_ifstream : public basic_istream<Elem, Tr>
template <class Elem, class Tr = char_traits<Elem> >
class basic_ofstream : public basic_ostream<Elem, Tr>

Они очень похожи – шаблонные классы с двумя параметрами: Elem – собственно, тип вводимо-выводимых элементов, и Tr – класс символьных характеристик этих элементов. Tr обязательно должен быть типа char_traits (лучше оставлять указанное по умолчанию char_traits<Elem>).
Остальные имена из приведенного выше списка – всего лишь специализации этих классов; например, классы ofstream и wofstream, отвечающие, соответственно, за вывод в файл последовательностей char- и wchar-символов, определяются так:
typedef basic_ofstream<char, char_traits<char> > ofstream;
typedef basic_ofstream
<wchar_t, char_traits<wchar_t> > wofstream;
В следующий раз посмотрим, как вся эта радость работает.

Комментариев нет: