Упражнения
* ГЛАВА 10. ПОТОКИ
"Доступно только то, что видимо" Б. Керниган В языке С++ нет средств для ввода-вывода. Их и не нужно, посколькутакие средства можно просто и элегантно создать на самом языке.Описанная здесь библиотека потокового ввода-вывода реализует строгийтиповой и вместе с тем гибкий и эффективный способ символьного ввода ивывода целых, вещественных чисел и символьных строк, а также являетсябазой для расширения, рассчитанного на работу с пользовательскими типамиданных. Пользовательский интерфейс библиотеки находится в файле<iostream.h>. Эта глава посвящена самой потоковой библиотеке, некоторымспособам работы с ней и определенным приемам реализации библиотеки.ВВЕДЕНИЕ
Широко известна трудность задачи проектирования и реализациистандартных средств ввода-вывода для языков программирования.Традиционно средства ввода-вывода были рассчитаны исключительно нанебольшое число встроенных типов данных. Однако, в нетривиальныхпрограммах на С++ есть много пользовательских типов данных, поэтомунеобходимо предоставитьвозможность ввода-вывода значений таких типов. Очевидно, что средстваввода-вывода должны быть простыми, удобными, надежными в использованиии, что важнее всего, адекватными. Пока никто не нашел решения, котороеудовлетворило бы всех; поэтому необходимо дать возможность пользователюсоздавать иные средства ввода-вывода, а также расширять стандартныесредства ввода-вывода в расчете на определенное применение. Цель создания С++ была в том, чтобы пользователь мог определить новыетипы данных, работа с которыми была бы столь же удобна и эффективна каки со встроенными типами. Таким образом, кажется разумным потребовать,чтобы средства ввода-вывода для С++ программировались с использованиемвозможностей С++, доступных каждому. Представленные здесь потоковыесредства ввода-вывода появились в результате попытки удовлетворитьэтим требованиям.Основная задача потоковых средств ввода-вывода - это процесспреобразования объектов определенного типа в последовательность символови наоборот. Существуют и другие схемы ввода-вывода, но указанная являетсяосновной, и если считать символ просто набором битов, игнорируя егоестественную связь с алфавитом, то многие схемы двоичного ввода-выводаможно свести к ней. Поэтому программистская суть задачи сводится кописанию связи между объектом определенного типа и бестиповой (чтосущественно) строкой. Последующие разделы описывают основные части потоковой библиотеки С++: 10.2 Вывод: То, что для прикладной программы представляется выводом, на самом деле является преобразованием таких объектов как int, char *, complex или Employee_record в последовательность символов. Описываются средства для записи объектов встроенных и пользовательских типов данных.10.3 Ввод: Описаны функции для ввода символов, строк и значений встроенных и пользовательских типов данных.10.4 Форматирование: Часто существуют определенные требования к виду вывода, например, int должно печататься десятичными цифрами, указатели в шестнадцатеричной записи, а вещественные числа должны быть с явно заданной точностью фиксированного размера. Обсуждаются функции форматирования и определенные программистские приемы их создания, в частности, манипуляторы.10.5 Файлы и потоки: Каждая программа на С++ может использовать по умолчанию три потока - стандартный вывод (cout), стандартный ввод (cin) и стандартный поток ошибок (cerr). Чтобы работать с какими- либо устройствами или файлами надо создать потоки и привязать их к этим устройствам или файлам. Описывается механизм открытия и закрытия файлов и связывания файлов с потоками.10.6 Ввод-вывод для С: обсуждается функция printf из файла <stdio.h> для С а также связь между библиотекой для С и <iostream.h> для С++.Укажем, что существует много независимых реализацийпотоковой библиотеки ввода-вывода и набор средств, описанных здесь, будеттолько подмножеством средств, имеющихся в вашей библиотеке. Говорят,что внутри любой большой программы есть маленькая программа, котораястремится вырваться наружу. В этой главе предпринята попытка описатькак раз маленькую потоковую библиотеку ввода-вывода, которая позволитоценить основные концепции потокового ввода-вывода и познакомитьс наиболее полезными средствами. Используя только средства,описанные здесь, можно написать много программ; если возникнетнеобходимость в более сложных средствах, обратитесь за деталями к вашемуруководству по С++. Заголовочный файл <iostream.h> определяет интерфейспотоковой библиотеки. В ранних версиях потоковой библиотеки использовалсяфайл <stream.h>. Если существуют оба файла, <iostream.h> определяет полныйнабор средств, а <stream.h> определяет подмножество, котороесовместимо с ранними, менее богатыми потоковыми библиотеками. Естественно, для пользования потоковой библиотекой вовсе не нужнознание техники ее реализации, тем более, что техника может бытьразличной для различных реализаций. Однако, реализация ввода-выводаявляется задачей, диктующей определенные условия, значит приемы, найденныев процессе ее решения, можно применить и для других задач, а само эторешение достойно изучения.ВЫВОД
Строгую типовую и единообразную работу как со встроенными, так и спользовательскими типами можно обеспечить, если использоватьединственное перегруженное имя функции для различных операций вывода.Например: put(cerr,"x = "); // cerr - выходной поток ошибок put(cerr,x); put(cerr,'\n'); Тип аргумента определяет какую функцию надо вызывать в каждом случае.Такой подход применяется в нескольких языках, однако, это слишкомдлинная запись. За счет перегрузки операции <<, чтобы она означала"вывести" ("put to"), можно получить более простую запись и разрешитьпрограммисту выводить в одном операторе последовательность объектов,например так: cerr << "x = " << x << '\n'; Здесь cerr обозначает стандартный поток ошибок. Так, если х типа intсо значением 123, то приведенный оператор выдаст x = 123 и еще символ конца строки в стандартный поток ошибок. Аналогично, если химеет пользовательский тип complex со значением (1,2.4), то указанныйоператор выдаст x = (1,2.4) в поток cerr. Такой подход легко использовать пока x такого типа, длякоторого определена операция <<, а пользователь может простодоопределить << для новых типов. Мы использовали операцию вывода, чтобы избежать многословности,неизбежной, если применять функцию вывода. Но почему именно символ <<?Невозможно изобрести новую лексему (см. 7.2). Кандидатом для ввода ивывода была операция присваивания, но большинство людей предпочитает,чтобы операции ввода и вывода были различны. Более того, порядоквыполнения операции = неподходящий, так cout=a=b означает cout=(a=b).Пробовали использовать операции < и >, но к ним так крепко привязанопонятие "меньше чем" и "больше чем", что операции ввода-вывода с нимиво всех практически случаях не поддавались прочтению. Операции << и >> похоже не создают таких проблем. Они асиметричны,что позволяет приписывать им смысл "в" и "из". Они не относятся к числунаиболее часто используемых операций над встроенными типами, априоритет << достаточно низкий, чтобы писать арифметические выражения вкачестве операнда без скобок: cout << "a*b+c=" << a*b+c << '\n'; Скобки нужны, если выражение содержит операции с более низкимприоритетом: cout << "a^b|c=" << (a^b|c) << '\n'; Операцию сдвига влево можно использовать в операции вывода, но, конечно,она должна быть в скобках: cout << "a<<b=" << (a<<b) << '\n';Вывод встроенных типов
Для управления выводом встроенных типов определяется класс ostreamс операцией << (вывести): class ostream: public virtual ios { //... public: ostream& operator<<(const char*); //строки ostream& operator<<(char); ostream& operator<<(short i) { return *this << int(i); } ostream& operator<<(int); ostream& operator<<(long); ostream& operator<<(double); ostream& operator<<(const void*); // указатели //... }; Естественно, в классе ostream должен быть набор функций operator<<()для работы с беззнаковыми типами. Функция operator<< возвращает ссылку на класс ostream, изкоторого она вызывалась, чтобы к ней можно было применить еще разoperator<<. Так, если х типа int, то cerr << "x = " << x; понимается как (cerr.operator<<("x = ")).operator<<(x); В частности, это означает, что если несколько объектов выводятся спомощью одного оператора вывода, то они будут выдаваться вестественном порядке: слева - направо. Функция ostream::operator<<(int) выводит целые значения, афункция ostream::operator<<(char) - символьные. Поэтому функция void val(char c) { cout << "int('"<< c <<"') = " << int(c) << '\n'; } печатает целые значения символов и с помощью программы main() { val('A'); val('Z'); } будет напечатано int('A') = 65 int('Z') = 90 Здесь предполагается кодировка символов ASCII, на вашей машине может бытьиной результат. Обратите внимание, что символьная константа имееттип char, поэтому cout<<'Z' напечатает букву Z, а вовсе не целое 90. Функция ostream::operator<<(const void*) напечатает значениеуказателя в такой записи, которая более подходит для используемойсистемы адресации.Программа main() { int i = 0; int* p = new int(1); cout << "local " << &i << ", free store " << p << '\n'; } выдаст на машине, используемой автором, local 0x7fffead0, free store 0x500c Для других систем адресации могут быть иные соглашения об изображениизначений указателей. Обсуждение базового класса ios отложим до 10.4.1.