На этом шаге мы рассмотрим особенности использования стандартных потоков ввода-вывода.
У читателя может возникнуть законный вопрос: почему в программах предыдущих шагов использовались потоки ввода-вывода cin, cout, cerr и ничего не требовалось знать о тех классах, к которым они относятся. Достаточно поместить в текст программы препроцессорную процедуру:
#include <iostream.h>
и можно с помощью операций включения (записи) данных в поток << и извлечения (чтения) данных из потока >> выполнять обмен с дисплеем и клавиатурой ЭВМ.
Объясняется это тем, что заголовочный файл iostream.h не только подключает к программе описания классов ios, istream, ostream, stream, но и содержит определения стандартных потоков ввода-вывода:
Каждый раз при включении в программу файла iostream.h происходит формирование объектов cin, cout, cerr, clog, т.е. создаются соответствующие стандартные потоки, и программисту становятся доступными связанные с ними средства ввода-вывода. Программа может по своему усмотрению разорвать связь любого из перечисленных объектов с консолью и соединить его с тем или иным файлом, но стандартная (по умолчанию) связь устанавливается именно с клавиатурой (поток cin) и дисплеем (потоки cout, cerr, clog). В том же файле iostream.h, где описаны классы istream, ostream, для них определены оригинальные операции ввода и вывода данных. Операция ввода класса istream называется извлечением (чтением) данных из потока. Она обозначается с помощью символа операции сдвига вправо <<. Операция вывода класса ostream называется вставкой или включением (или записью) данных в поток. Она обозначается с помощью символа операции сдвига влево >>. Роль операции извлечения и вставки конструкции << и >> играют по умолчанию только в том случае, если слева от них находятся объекты, соответственно, классов iostream и ostream:
cin >> имя_объекта_базового_типа cout << выражение_базового_типа cerr << выражение_базового_типа clog << выражение_базового_типа
Выполнение операции >> (извлечение из потока) заключается в преобразовании последовательности символов потока в значение типизированного объекта, частным случаем которого является переменная базового типа int, long, double и т.д. При выполнении операции << (включение в поток) осуществляется обратное преобразование - типизированное значение выражения (int, float, char и т.д.) трансформируется в последовательность символов потока. Примеры применении операций включения в поток и извлечения из потока типизированных значений уже приводились многократно. Хорошо бы теперь читателю удивиться некоторым особенностям ввода-вывода с помощью этих операций.
Дело в том, что внешнее (визуальное) представление данных никак не похоже на те внутренние коды, которые используются для их хранения и обработки внутри ЭВМ. Вне ЭВМ это алфавитно-цифровые изображения (чисел или символов), внутри ЭВМ это двоичные коды - последовательности битов (двоичных разрядов) регламентированной для каждого типа длины. При выполнении программы операция вывода данных (например, на экран дисплея) предусматривает преобразование двоичных кодов в символы алфавита, изображаемые на внешнем устройстве. В операции ввода выполняется преобразование сигналов от клавишей клавиатуры в двоичные коды внутреннего представления данных. Чтобы отвлечься (абстрагироваться) от особенностей аппаратурной реализации устройств ввода-вывода, программист, работая на таком языке, как С++, использует входные и выходные потоки. Поток в обоих случаях - это последовательность байтов (двоичных кодов фиксированной длины). При выводе коды потока могут изображаться на экране в виде символов принятого алфавита. При вводе по кодам из потока формируются двоичные представления вводимых данных. Сложности и вопросы появляются у программиста при преобразовании данных (кодов) из внешнего (потокового) во внутреннее представление и обратно.
Например, во внутреннем коде целое число может быть представлено двумя смежными байтами. Те же самые смежные байты можно рассматривать как внутренние коды двух литер (символов). (Именно так действует объединение union в языке С++, позволяя по-разному интерпретировать внутренние коды данных.)
Средства вывода, применяемые в языке, должны иметь возможность распознать тип выводимых данных и в указанном примере поместить в выходной поток либо код внешнего представления целого числа, либо два кода расположенных рядом символов. Для обеспечения такой возможности операция << включения в стандартный выходной поток перегружена. Существуют ее варианты для типов char, unsigned short, signed short, signed int, unsigned int, signed long, unsigned long, float double, long double, char *, void *. Все они доступны после включения в программу файла iostream.h. Отметим, что операция включения определена только для указателей двух типов. Этого вполне достаточно, так как все указатели, отличные от char *, автоматически приводятся к типу void *.
На следующем шаге мы более подробно остановимся на обмене данными со стандартными потоками.