Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 80 81 82 [ 83 ] 84 85 86 ... 120


библиотекой для С и <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,



1 ... 80 81 82 [ 83 ] 84 85 86 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика