|
Программирование >> Поддержка объектно-ориентированного программирования
библиотекой для С и <iostream.h> для С++. Укажем, что существует много независимых реализаций потоковой библиотеки ввода-вывода и набор средств, описанных здесь, будет только подмножеством средств, имеющихся в вашей библиотеке. Говорят, что внутри любой большой программы есть маленькая программа, которая стремится вырваться наружу. В этой главе предпринята попытка описать как раз маленькую потоковую библиотеку ввода-вывода, которая позволит оценить основные концепции потокового ввода-вывода и познакомить с наиболее полезными средствами. Используя только средства, описанные здесь, можно написать много программ; если возникнет необходимость в более сложных средствах, обратитесь за деталями к вашему руководству по С++. Заголовочный файл <iostream.h> определяет интерфейс потоковой библиотеки. В ранних версиях потоковой библиотеки использовался файл <stream.h>. Если существуют оба файла, <iostream.h> определяет полный набор средств, а <stream.h> определяет подмножество, которое совместимо с ранними, менее богатыми потоковыми библиотеками. Естественно, для пользования потоковой библиотекой вовсе не нужно знание техники ее реализации, тем более, что техника может быть различной для различных реализаций. Однако, реализация ввода-вывода является задачей, диктующей определенные условия, значит приемы, найденные в процессе ее решения, можно применить и для других задач, а само это решение достойно изучения. 10.2 ВЫВОД Строгую типовую и единообразную работу как со встроенными, так и с пользовательскими типами можно обеспечить, если использовать единственное перегруженное имя функции для различных операций вывода. Например: put(cerr, x = ); cerr - выходной поток ошибок put(cerr,x); put(cerr,\n); Тип аргумента определяет какую функцию надо вызывать в каждом случае. Такой подход применяется в нескольких языках, однако, это слишком длинная запись. За счет перегрузки операции << , чтобы она означала вывести ( put to ), можно получить более простую запись и разрешить программисту выводить в одном операторе последовательность объектов, например так: cerr << x = << x << \n; Здесь cerr обозначает стандартный поток ошибок. Так, если х типа int со значением 1 23, то приведенный оператор выдаст 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 << abc= << (abjc) << \n; Операцию сдвига влево можно использовать в операции вывода, но, конечно, она должна быть в скобках: cout << a<<b= << (a<<b) << \n; 10.2.1 Вывод встроенных типов Для управления выводом встроенных типов определяется класс 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 отложим до 1 0.4.1 . 10.2.2 Вывод пользовательских типов Рассмотрим пользовательский тип данных: class complex { double re, im; public: complex(double r = 0, double i = 0) { re=r; im=i; } friend double real(complex& a) { return a.re; } friend double imag(complex& a) { return a.im; } friend complex operator+(complex, complex); friend complex operator-(complex, complex); friend complex operator*(complex, complex); friend complex operator/(complex, complex); ... Для нового типа complex операцию << можно определить так: ostream& operator<<(ostream&s, complex z) return s << ( real(z) << , << imag(z) << ); и использовать как operator<< для встроенных типов. Например, main() complex x(1,2); cout << x = << x << \n; выдаст x = (1,2) Для определения операции вывода над пользовательскими типами данных не нужно модифицировать описание класса ostream, не требуется и доступ к структурам данных, скрытым в описании класса. Последнее очень кстати, поскольку описание класса ostream находится среди стандартных заголовочных файлов, доступ по записи к которым закрыт для большинства пользователей, и изменять которые они вряд ли захотят, даже если бы могли. Это важно и по той причине, что дает защиту от случайной порчи этих структур данных. Кроме того имеется возможность изменить реализацию ostream,
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |