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




Операцию ввода для пользовательского типа можно определить в точноститак же, как и операцию вывода, но для операции ввода существенно, чтобывторой параметр имел тип ссылки, например: istream& operator>>(istream& s, complex& a) /* формат input рассчитан на complex; "f" обозначает float: f (f) (f, f) */ { double re = 0, im = 0; char c = 0; s >> c; if (c == '(') { s >> re >> c; if (c == ',') s >> im >> c; if (c!= ')') s.clear(ios::badbit); // установим состояние } else { s.putback(c); s >> re; } if (s) a = complex(re,im); return s; } Несмотря на сжатость кода, обрабатывающего ошибки, на самом делеучитывается большая часть ошибок. Инициализация локальной переменнойс нужна для того, чтобы в нее не попало случайное значение, например'(', в случае неудачной операции. Последняя проверка состояния потокагарантирует, что параметр a получит значение только при успешном вводе. Операция, устанавливающая состояние потока, названа clear()(здесь clear - ясный, правильный),поскольку чаще всего она используется для восстановления состояния потокакак good(); значением по умолчанию для параметра ios::clear() являетсяios::goodbit.

Форматирование

Все примеры из 10.2 содержали неформатированный вывод, который являлсяпреобразованием объекта в последовательность символов, задаваемуюстандартными правилами, длина которой также определяется этимиправилами. Часто программистам требуются более развитые возможности.Так, возникает потребность контролировать размер памяти, необходимойдля операции вывода, и формат, используемый для выдачи чисел.Точно так же допустимо управление некоторыми аспектами ввода.

Класс ios

Большинство средств управления вводом-выводом сосредоточены в классеios, который является базовым для ostream и istream. По сути здесьнаходится управление связью между istream или ostream и буфером,используемым для операций ввода-вывода. Именно класс ios контролирует:как символы попадают в буфер и как они выбираются оттуда. Так, в классеios есть член, содержащий информацию об используемой при чтении илизаписи целых чисел системы счисления (десятичная, восьмеричная илишестнадцатеричная), о точности вещественных чисел и т.п., а такжефункции для проверки и установки значений переменных, управляющихпотоком. class ios { //... public: ostream* tie(ostream* s); // связать input и output ostream* tie(); // возвратить "tie" int width(int w); // установить поле width int width() const; char fill(char); // установить символ заполнения char fill() const; // вернуть символ заполнения long flags(long f); long flags() const; long setf(long setbits, long field); long setf(long); long unsetf(long); int precision(int); // установить точность для float int precision() const; int rdstate(); const; // состояния потоков, см. $$10.3.2 int eof() const; int fail() const; int bad() const; int good() const; void clear(int i=0); //... }; В 10.3.2 описаны функции, работающие с состоянием потока, остальныеприведены ниже.

Связывание потоков

Функция tie() может установить и разорвать связь между ostream иistream. Рассмотрим пример: main() { String s; cout << "Password: "; cin >> s; //... } Как можно гарантировать, что приглашение Password: появится наэкране прежде, чем выполниться операция чтения? Вывод в cout и вводиз cin буферизуются, причем независимо, поэтому Password: появитсятолько по завершении программы, когда закроется буфер вывода. Решение состоит в том, чтобы связать cout и cin с помощьюоперации cin.tie(cout).Если ostream связан с потоком istream, то буфер вывода выдается прикаждой операции ввода над istream. Тогда операции cout << "Password: "; cin >> s; эквивалентны cout << "Password: "; cout.flush(); cin >> s; Обращение is.tie(0) разрывает связь между потоком is и потоком, скоторым он был связан, если такой был. Подобно другим потоковымфункциям, устанавливающим определенное значение, tie(s) возвращаетпредыдущее значение, т.е. значение связанного потока перед обращениемили 0. Вызов без параметра tie() возвращает текущее значение.

Поля вывода

Функция width() устанавливает минимальное число символов, использующеесяв последующей операции вывода числа или строки. Так в результатеследующих операций cout.width(4); cout << '(' << 12 << ')'; получим число 12 в поле размером 4 символа, т.е. (12) Заполнение поля заданными символами или выравнивание можно установить спомощью функции fill(), например: cout.width(4); cout.fill('#'); cout << '(' << "ab" << ')'; напечатает (##ab) По умолчанию поле заполняется пробелами, а размер поля по умолчаниюесть 0, что означает "столько символов, сколько нужно". Вернуть размеруполя стандартное значение можно с помощью вызова cout.width(0); // ``столько символов, сколько надо'' Функция width() задает минимальное число символов. Если появится большесимволов, они будут напечатаны все, поэтому cout.width(4); cout << '(' << "121212" << ")\n"; напечатает (121212) Причина, по которой разрешено переполнение поля, а не усечение вывода,в том, чтобы избежать зависания при выводе. Лучше получить правильнуювыдачу, выглядящую некрасиво, чем красивую выдачу, являющуюсянеправильной. Вызов width() влияет только на одну следующую за ним операциювывода, поэтому cout.width(4); cout.fill('#'); cout << '(' << 12 << "),(" << '(' <<12 << ")\n"; напечатает (##12),(12) а не (##12),(##12) как можно было бы ожидать. Однако, заметьте, что если бы влияниераспространялось на все операции вывода чисел и строк, получился быеще более неожиданный результат: (##12#),(##12#) С помощью стандартного манипулятора, показанного в 10.4.2.1, можно болееэлегантно задавать размера поля вывода.

Состояние формата

В классе ios содержится состояние формата, которое управляетсяфункциями flags() и setf(). По сути эти функции нужны, чтобыустановить или отменить следующие флаги: class ios { public: // управляющие форматом флаги: enum { skipws=01, // пропуск обобщенных пробелов для input // поле выравнивания: left=02, // добавление перед значением right=04, // добавление после значения internal=010, // добавление между знаком и значением // основание целого: dec=020, // восьмеричное oct=040, // десятичное hex=0100, // шестнадцатеричное showbase=0200, // показать основание целого showpoint=0400, // выдать нули в конце uppercase=01000, // 'E', 'X', а не 'e', 'x' showpos=02000, // '+' для положительных чисел // запись числа типа float: scientific=04000, //.dddddd Edd fixed=010000, // dddd.dd // сброс в выходной поток: unitbuf=020000, // после каждой операции stdio=040000 // после каждого символа }; //... }; Смысл флагов будет разъяснен в последующих разделах. Конкретныезначения флагов зависят от реализации и даны здесь только для того,чтобы избежать синтаксически неверных конструкций. Определение интерфейса как набора флагов и операций для ихустановки или отмены - это оцененный временем, хотя и несколькоустаревший прием. Основное его достоинство в том, что пользовательможет собрать воедино набор флагов, например, так: const int my_io_options = ios::left|ios::oct|ios::showpoint|ios::fixed; Такое множество флагов можно задавать как параметр одной операции cout.flags(my_io_options); а также просто передавать между функциями одной программы: void your_function(int ios_options); void my_function() { //... your_function(my_io_options); //... } Множество флагов можно установить с помощью функции flags(), например: void your_function(int ios_options) { int old_options = cout.flags(ios_options); //... cout.flags(old_options); // reset options } Функция flags() возвращает старое значение множества флагов. Этопозволяет переустановить значения всех флагов, как показано выше,а также задать значение отдельному флагу. Например вызов myostream.flags(myostream.flags()|ios::showpos); заставляет класс myostream выдавать положительные числа со знаком+ и, в то же время, не меняет значения других флагов. Получаетсястарое значение множества флагов, к которому добавляется с помощьюоперации | флаг showpos. Функция setf() делает то же самое,поэтому эквивалентная запись имеет вид myostream.setf(ios::showpos); После установки флаг сохраняет значение до явной отмены. Все-таки управление вводом-выводом с помощью установки и отменыфлагов - грубое и ведущее к ошибкам решение. Если только вы тщательноне изучите свое справочное руководство и не будете применять флагитолько в простых случаях, как это делается в последующих разделах, толучше использовать манипуляторы (описанные в 10.4.2.1). Приемы работыс состоянием потока лучше изучить на примере реализации класса, чемизучая интерфейс класса.

Вывод целых

Прием задания нового значения множества флагов с помощью операции | ифункций flags() и setf() работает только тогда, когда один бит определяетзначение флага. Не такая ситуация при задании системы счисления целыхили вида выдачи вещественных. Здесь значение, определяющее вид выдачи,нельзя задать одним битом или комбинацией отдельных битов. Решение, принятое в <iostream.h>, сводится к использованиюверсии функции setf(), работающей со вторым "псевдопараметром", которыйпоказывает какой именно флаг мы хотим добавить к новому значению. Поэтому обращения cout.setf(ios::oct,ios::basefield); // восьмеричное cout.setf(ios::dec,ios::basefield); // десятичное cout.setf(ios::hex,ios::basefield); // шестнадцатеричное установят систему счисления, не затрагивая других компонентов состоянияпотока. Если система счисления установлена, она используется до явнойпереустановки, поэтому cout << 1234 << ' '; // десятичное по умолчанию cout << 1234 << ' '; cout.setf(ios::oct,ios::basefield); // восьмеричное cout << 1234 << ' '; cout << 1234 << ' '; cout.setf(ios::hex,ios::basefield); // шестнадцатеричное cout << 1234 << ' '; cout << 1234 << ' '; напечатает 1234 1234 2322 2322 4d2 4d2 Если появится необходимость указывать систему счисления для каждоговыдаваемого числа, следует установить флаг showbase. Поэтому, добавивперед приведенными выше обращениями cout.setf(ios::showbase); мы получим 1234 1234 02322 02322 0x4d2 0x4d2 Стандартные манипуляторы, приведенные в $$10.4.2.1, предлагают болееэлегантный способ определения системы счисления при выводе целых.

Выравнивание полей

С помощью обращений к setf() можно управлять расположением символовв пределах поля: cout.setf(ios::left,ios::adjustfield); // влево cout.setf(ios::right,ios::adjustfield); // вправо cout.setf(ios::internal,ios::adjustfield); // внутреннее Будет установлено выравнивание в поле вывода, определяемом функциейios::width(), причем не затрагивая других компонентов состояния потока. Выравнивание можно задать следующим образом: cout.width(4); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::left,ios::adjustfield); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::internal,ios::adjustfield); cout << '(' << -12 << "\n"; что выдаст (-12) (-12) (- 12) Если установлен флаг выравнивания internal (внутренний), то символыдобавляются между знаком и величиной. Как видно, стандартным являетсявыравнивание вправо.

Вывод плавающих чисел.

Вывод вещественных величин также управляется с помощью функций,работающих с состоянием потока. В частности, обращения: cout.setf(ios::scientific,ios::floatfield); cout.setf(ios::fixed,ios::floatfield); cout.setf(0,ios::floatfield); // вернуться к стандартному установят вид печати вещественных чисел без изменения другихкомпонентов состояния потока.Например: cout << 1234.56789 << '\n'; cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << '\n'; cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << '\n'; напечатает 1234.57 1.234568e+03 1234.567890 После точки печатается n цифр, как задается в обращении cout.precision(n) По умолчанию n равно 6. Вызов функции precision влияет на все операцииввода-вывода с вещественными до следующего обращения к precision,поэтому cout.precision(8); cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; cout.precision(4); cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; выдаст 1234.5679 1234.5679 1235 1235 Заметьте, что происходит округление, а не отбрасывание дробной части. Стандартные манипуляторы, введенные в $$10.4.2.1, предлагаютболее элегантный способ задания формата вывода вещественных.

Манипуляторы

К ним относятся разнообразные операции, которые приходитсяприменять сразу перед или сразу после операции ввода-вывода. Например: cout << x; cout.flush(); cout << y; cin.eatwhite(); cin >> x; Если писать отдельные операторы как выше, то логическая связь междуоператорами неочевидна, а если утеряна логическая связь, программутруднее понять. Идея манипуляторов позволяет такие операции как flush() илиeatwhite() прямо вставлять в список операций ввода-вывода. Рассмотримоперацию flush(). Можно определить класс с операцией operator<<(), вкотором вызывается flush(): class Flushtype { }; ostream& operator<<(ostream& os, Flushtype) { return flush(os); } определить объект такого типа Flushtype FLUSH; и добиться выдачи буфера, включив FLUSH в список объектов, подлежащихвыводу: cout << x << FLUSH << y << FLUSH; Теперь установлена явная связь между операциями вывода и сбрасываниябуфера. Однако, довольно быстро надоест определять класс и объект длякаждой операции, которую мы хотим применить к поточной операции вывода.К счастью, можно поступить лучше. Рассмотрим такую функцию: typedef ostream& (*Omanip) (ostream&); ostream& operator<<(ostream& os, Omanip f) { return f(os); } Здесь операция вывода использует параметры типа "указатель на функцию,имеющую аргумент ostream& и возвращающую ostream&". Отметив, что flush()есть функция типа "функция с аргументом ostream& и возвращающаяostream&", мы можем писать cout << x << flush << y << flush; получив вызов функции flush(). На самом деле в файле <iostream.h>функция flush() описана как ostream& flush(ostream&); а в классе есть операция operator<<, которая использует указатель нафункцию, как указано выше: class ostream: public virtual ios { //... public: ostream& operator<<(ostream& ostream& (*)(ostream&)); //... }; В приведенной ниже строке буфер выталкивается в поток cout дважды вподходящее время: cout << x << flush << y << flush; Похожие определения существуют и для класса istream: istream& ws(istream& is) { return is.eatwhite(); } class istream: public virtual ios { //... public: istream& operator>>(istream&, istream& (*) (istream&)); //... }; поэтому в строке cin >> ws >> x; действительно обобщенные пробелы будут убраны до попытки чтения в x.Однако, поскольку по умолчанию для операции >> пробелы "съедаются" итак, данное применение ws() избыточно. Находят применение и манипуляторы с параметрами. Например,может появиться желание с помощью cout << setprecision(4) << angle; напечатать значение вещественной переменной angle с точностью дочетырех знаков после точки. Для этого нужно уметь вызывать функцию, которая установитзначение переменной, управляющей в потоке точностью вещественных.Это достигается, если определить setprecision(4) как объект, которыйможно "выводить" с помощью operator<<(): class Omanip_int { int i; ostream& (*f) (ostream&,int); public: Omanip_int(ostream& (*ff) (ostream&,int), int ii): f(ff), i(ii) { } friend ostream& operator<<(ostream& os, Omanip& m) { return m.f(os,m.i); } }; Конструктор Omanip_int хранит свои аргументы в i и f, а с помощьюoperator<< вызывается f() с параметром i. Часто объекты таких классовназывают объект-функция. Чтобы результат строки cout << setprecision(4) << angle был таким, как мы хотели, необходимо чтобы обращение setprecision(4)создавало безымянный объект класса Omanip_int, содержащий значение 4и указатель на функцию, которая устанавливает в потоке ostream значениепеременной, задающей точность вещественных: ostream& _set_precision(ostream&,int); Omanip_int setprecision(int i) { return Omanip_int(&_set_precision,i); } Учитывая сделанные определения, operator<<() приведет к вызовуprecision(i). Утомительно определять классы наподобие Omanip_int для всехтипов аргументов, поэтому определим шаблон типа: template<class T> class OMANIP { T i; ostream& (*f) (ostream&,T); public: OMANIP(ostream (*ff) (ostream&,T), T ii): f(ff), i(ii) { } friend ostream& operator<<(ostream& os, OMANIP& m) { return m.f(os,m.i) } }; С помощью OMANIP пример с установкой точности можно сократить так: ostream& precision(ostream& os,int) { os.precision(i); return os; } OMANIP<int> setprecision(int i) { return OMANIP<int>(&precision,i); } В файле <iomanip.h> можно найти шаблон типа OMANIP, его двойник дляistream - шаблон типа SMANIP, а SMANIP - двойник для ioss.Некоторые из стандартных манипуляторов, предлагаемых поточнойбиблиотекой, описаны ниже. Отметим,что программист может определить новыенеобходимые ему манипуляторы, не затрагивая определений istream,ostream, OMANIP или SMANIP. Идею манипуляторов предложил А. Кениг. Его вдохновили процедурыразметки (layout) системы ввода-вывода Алгола68. Такая техника имеетмного интересных приложений помимо ввода-вывода. Суть ее в том, чтосоздается объект, который можно передавать куда угодно и которыйиспользуется как функция. Передача объекта является более гибкимрешением, поскольку детали выполнения частично определяются создателемобъекта, а частично тем, кто к нему обращается.


Поделиться:




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

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


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