straustrup2 (852740), страница 67
Текст из файла (страница 67)
В этой главе предпринята попытка описать как раз маленькую потоковую библиотекуввода-вывода, которая позволит оценить основные концепции потокового ввода-вывода и познакомитьс наиболее полезными средствами. Используя только средства, описанные здесь, можно написатьмного программ; если возникнетнеобходимость в более сложных средствах, обратитесь за деталями к вашему руководству по С++.Заголовочный файл <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 со значением 123, топриведенный оператор выдастx = 123и еще символ конца строки в стандартный поток ошибок.
Аналогично, если х имеет пользовательскийтип complex со значением (1,2.4), то указанный оператор выдастx = (1,2.4)в поток cerr. Такой подход легко использовать пока x такого типа, для которого определена операция<<, а пользователь может просто доопределить << для новых типов.Мы использовали операцию вывода, чтобы избежать многословности, неизбежной, если применятьфункцию вывода.
Но почему именно символ << ? Невозможно изобрести новую лексему (см. 7.2).Кандидатом для ввода и вывода была операция присваивания, но большинство людей предпочитает,чтобы операции ввода и вывода были различны. Более того, порядок выполнения операции =неподходящий, так cout=a=b означает cout=(a=b). Пробовали использовать операции < и >, но к ним таккрепко привязано понятие "меньше чем" и "больше чем", что операции ввода-вывода с ними во всехпрактически случаях не поддавались прочтению.Операции << и >> похоже не создают таких проблем.
Они асиметричны, что позволяет приписывать имсмысл "в" и "из". Они не относятся к числу наиболее часто используемых операций над встроенными258Бьерн Страуструп.Язык программирования С++типами, а приоритет << достаточно низкий, чтобы писать арифметические выражения в качествеоперанда без скобок:cout << "a*b+c=" << a*b+c << '\n';Скобки нужны, если выражение содержит операции с более низким приоритетом:cout << "a^b|c=" << (a^b|c) << '\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') = 65int('Z') = 90259Бьерн Страуструп.Язык программирования С++Здесь предполагается кодировка символов 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.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,260Бьерн Страуструп.Язык программирования С++не затрагивая пользовательских программ.10.3 ВВОДВвод во многом сходен с выводом. Есть класс istream, который реализует операцию ввода >> ("ввестииз" - "input from") для небольшого набора стандартных типов.
Для пользовательских типов можноопределить функцию operator>>.10.3.1 Ввод встроенных типовКласс istream определяется следующим образом:class istream//...public:istream&istream&istream&istream&istream&istream&istream&//...};: public virtual ios {operator>>(char*);operator>>(char&);operator>>(short&);operator>>(int&);operator>>(long&);operator>>(float&);operator>>(double&);// строка// символФункции ввода operator>> определяются так:istream& istream::operator>>(T& tvar){// пропускаем обобщенные пробелы// каким-то образом читаем T в`tvar'return *this;}Теперь можно ввести в VECTOR последовательность целых, разделяемых пробелами, с помощьюфункции:int readints(Vector<int>& v)// возвращаем число прочитанных целых{for (int i = 0; i<v.size(); i++){if (cin>>v[i]) continue;return i;}// слишком много целых для размера Vector// нужна соответствующая обработка ошибки}Появление значения с типом, отличным от int, приводит к прекращению операции ввода, и цикл вводазавершается.
Так, если мы вводим1 2 3 4 5. 6 7 8.то функция readints() прочитает пять целых чисел1 2 3 4 5Символ точка останется первым символом, подлежащим вводу. Под пробелом, как определено встандарте С, понимается обобщенный пробел, т.е. пробел, табуляция, конец строки, перевод строкиили возврат каретки. Проверка на обобщенный пробел возможна с помощью функции isspace() изфайла <ctype.h>.261Бьерн Страуструп.Язык программирования С++В качестве альтернативы можно использовать функции get():class istream : public virtual ios {//...istream& get(char& c);istream& get(char* p, int n, char ='n');};// символ// строкаВ них обобщенный пробел рассматривается как любой другой символ и они предназначены для такихопераций ввода, когда не делается никаких предположений о вводимых символах.Функция istream::get(char&) вводит один символ в свой параметр.
Поэтому программу посимвольногокопирования можно написать так:main(){char c;while (cin.get(c)) cout << c;}Такая запись выглядит несимметрично, и у операции >> для вывода символов есть двойник под именемput(), так что можно писать и так:main(){char c;while (cin.get(c)) cout.put(c);}Функция с тремя параметрами istream::get() вводит в символьный вектор не менее n символов, начинаяс адреса p. При всяком обращении к get() все символы, помещенные в буфер (если они были),завершаются 0, поэтому если второй параметр равен n, то введено не более n-1 символов. Третийпараметр определяет символ, завершающий ввод. Типичное использование функции get() с тремяпараметрами сводится к чтению строки в буфер заданного размера для ее дальнейшего разбора,например так:void f(){char buf[100];cin >> buf;cin.get(buf,100,'\n');//...}// подозрительно// надежноОперация cin>>buf подозрительна, поскольку строка из более чем 99 символов переполнит буфер.
Еслиобнаружен завершающий символ, то он остается в потоке первым символом подлежащим вводу. Этопозволяет проверять буфер на переполнение:void f(){char buf[100];cin.get(buf,100,'\n');// надежноchar c;if (cin.get(c) && c!='\n') {// входная строка больше, чем ожидалось}//...}Естественно, существует версия get() для типа unsigned char.В стандартном заголовочном файле <ctype.h> определены несколько функций, полезных для обработкипри вводе:262Бьерн Страуструп.intintintintintintisalpha(char)isupper(char)islower(char)isdigit(char)isxdigit(char)isspace(char)int iscntrl(char)int ispunct(char)intintintintisalnum(char)isprint(char)isgraph(char)isascii(char c)Язык программирования С++// 'a'..'z' 'A'..'Z'// 'A'..'Z'// 'a'..'z'// '0'..'9'// '0'..'9' 'a'..'f' 'A'..'F'// ' ' '\t' возвращает конец строки// и перевод формата// управляющий символ в диапазоне// (ASCII 0..31 и 127)// знак пунктуации, отличен от// приведенных выше// isalpha() | isdigit()// видимый: ascii ' '..'~'// isalpha() | isdigit() | ispunct(){ return 0<=c && c<=127; }Все они, кроме isascii(), работают с помощью простого просмотра, используя символ как индекс втаблице атрибутов символов.