Вывод пользовательских типов




Рассмотрим пользовательский тип данных: 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, не затрагивая пользовательских программ.

ВВОД

Ввод во многом сходен с выводом. Есть класс istream, который реализуетоперацию ввода >> ("ввести из" - "input from") для небольшого наборастандартных типов. Для пользовательских типов можно определить функциюoperator>>.

Ввод встроенных типов

Класс istream определяется следующим образом: class istream: public virtual ios { //... public: istream& operator>>(char*); // строка istream& operator>>(char&); // символ istream& operator>>(short&); istream& operator>>(int&); istream& operator>>(long&); istream& operator>>(float&); istream& 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>. В качестве альтернативы можно использовать функции 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> определены несколькофункций, полезных для обработки при вводе: int isalpha(char) // 'a'..'z' 'A'..'Z' int isupper(char) // 'A'..'Z' int islower(char) // 'a'..'z' int isdigit(char) // '0'..'9' int isxdigit(char) // '0'..'9' 'a'..'f' 'A'..'F' int isspace(char) // ' ' '\t' возвращает конец строки // и перевод формата int iscntrl(char) // управляющий символ в диапазоне // (ASCII 0..31 и 127) int ispunct(char) // знак пунктуации, отличен от приведенных выше int isalnum(char) // isalpha() | isdigit() int isprint(char) // видимый: ascii ' '..'~' int isgraph(char) // isalpha() | isdigit() | ispunct() int isascii(char c) { return 0<=c && c<=127; } Все они, кроме isascii(), работают с помощью простого просмотра,используя символ как индекс в таблице атрибутов символов. Поэтомувместо выражения типа (('a'<=c && c<='z') || ('A'<=c && c<='Z')) // буква которое не только утомительно писать, но оно может быть и ошибочным(на машине с кодировкой EBCDIC оно задает не только буквы), лучшеиспользовать вызов стандартной функции isalpha(), который к томуже более эффективен.В качестве примера приведем функцию eatwhite(), которая читает изпотока обобщенные пробелы: istream& eatwhite(istream& is) { char c; while (is.get(c)) { if (isspace(c)==0) { is.putback(c); break; } } return is; } В ней используется функция putback(), которая возвращает символ впоток, и он становится первым подлежащим чтению.

Состояния потока

С каждым потоком (istream или ostream) связано определенное состояние.Нестандартные ситуации и ошибки обрабатываются с помощью проверки иустановки состояния подходящим образом.Узнать состояние потока можно с помощью операций над классом ios: class ios { //ios является базовым для ostream и istream //... public: int eof() const; // дошли до конца файла int fail() const; // следующая операция будет неудачна int bad() const; // поток испорчен int good() const; // следующая операция будет успешной //... }; Последняя операция ввода считается успешной, если состояние задаетсяgood() или eof(). Если состояние задается good(), то последующаяоперация ввода может быть успешной, в противном случае она будетнеудачной. Применение операции ввода к потоку в состоянии, задаваемомне good(), считается пустой операцией. Если произошла неудача припопытке чтения в переменную v, то значение v не изменилось (оно неизменится, если v имеет тип, управляемый функциями члена из istreamили ostream). Различие между состояниями, задаваемыми как fail() иликак bad() уловить трудно, и оно имеет смысл только для разработчиковопераций ввода. Если состояние есть fail(), то считается, что потокне поврежден, и никакие символы не пропали; о состоянии bad() ничегосказать нельзя. Значения, обозначающие эти состояния, определены в классе ios: class ios { //... public: enum io_state { goodbit=0, eofbit=1, filebit=2, badbit=4, }; //... }; Истинные значения состояний зависят от реализации, и указанные значенияприведены только, чтобы избежать синтаксически неправильных конструкций. Проверять состояние потока можно следующим образом: switch (cin.rdstate()) { case ios::goodbit: // последняя операция с cin была успешной break; case ios::eofbit: // в конце файла break; case ios::filebit: // некоторый анализ ошибки // возможно неплохой break; case ios::badbit: // cin возможно испорчен break; } В более ранних реализациях для значений состояний использовалисьглобальные имена. Это приводило к нежелательному засорениюпространства именования, поэтому новые имена доступны только в пределахкласса ios. Если вам необходимо использовать старые имена в сочетании сновой библиотекой, можно воспользоваться следующими определениями: const int _good = ios::goodbit; const int _bad = ios::badbit; const int _file = ios::filebit; const int _eof = ios::eofbit; typedef ios::io_state state_value; Разработчики библиотек должны заботится о том, чтобы не добавлятьновых имен к глобальному пространству именования. Если элементыперечисления входят в общий интерфейс библиотеки, они всегдадолжны использоваться в классе с префиксами, например, как ios::goodbitи ios::io_state. Для переменной любого типа, для которого определены операции<< и >>, цикл копирования записывается следующим образом: while (cin>>z) cout << z << '\n'; Если поток появляется в условии, то проверяется состояние потока, иусловие выполняется (т.е. результат его не 0) только для состоянияgood(). Как раз в приведенном выше цикле проверяется состояние потокаistream, что является результатом операции cin>>z. Чтобы узнать,почему произошла неудача в цикле или условии, надо проверить состояние.Такая проверка для потока реализуется с помощью операцииприведения (7.3.2). Так, если z является символьным вектором, то в приведенном циклечитается стандартный ввод и выдается для каждой строки стандартноговывода по одному слову (т.е. последовательности символов, не являющихсяобобщенными пробелами). Если z имеет тип complex, то в этом циклес помощью операций, определенных в 10.2.2 и 10.2.3, будут копироватьсякомплексные числа. Шаблонную функцию копирования для потоков созначениями произвольного типа можно написать следующим образом: complex z; iocopy(z,cin,cout); // копирование complex double d; iocopy(d,cin,cout); // копирование double char c; iocopy(c,cin,cout); // копирование char Поскольку надоедает проверять на корректность каждую операцию ввода-вывода, то распространенным источником ошибок являются именно те места впрограмме, где такой контроль существенен. Обычно операции вывода непроверяют, но иногда они могут завершиться неудачно. Потоковый ввод-вывод разрабатывался из того принципа, чтобы сделать исключительныеситуации легкодоступными, и тем самым упростить обработку ошибокв процессе ввода-вывода.


Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: