Определение пользовательских манипуляторов
В ситуации, когда нужно повторно определять одну и ту же команду форматирования, пользовательский манипулятор обеспечивает простую запись, нечто вроде макро. Манипулятор без параметра создается следующим образом. Определяется класс с функцией operator<<(), которая, в свою очередь, вызывает определенные функции форматирования.
Пример 6.7.1. Определим класс манипулятора class my_f{}; Перегрузим функцию-оператор operator<< для объекта класса манипулятора. Манипулятор позволит выполнить выравнивание по правому краю. По умолчанию выравнивание по левому краю. ostream& operator<<(ostream& out,my_f) { out.width(12); out.fill(‘# ’); cout.setf(ios:: right,ios::adjustfield); return out; } void main() {my_f MF; cout<<52.3456<<endl<<MF<<52.3456<<endl; } Синтаксис языка разрешает параметры без имен, если последние не используются в теле функции. В стандарте языка С++ отмечается, что использование специального параметра без имени «полезно для резервирования места в списке параметров». В дальнейшем этот параметр может быть введен в функции без изменения интерфейса, т.е. без изменения вызывающей программы. Такая возможность бывает удобной при развитии уже существующей программы за счет изменения входящих в нее функций. Так как манипулятор–это функция, принимающая и возвращающая ссылку на поток, то лучше определить манипулятор MF так: Пример 6.7.2.. ostream& MF(ostream& out) { return out<<resetiosflags(ios::right)<<setiosflags(ios::right); }
Эти два примера показывают два различных подхода к созданию пользовательских манипуляторов. В первом случае мы, по сути, не создавали манипулятор, а перегрузили операцию >> для класса my_f. Во втором случае мы создали собственно функцию-манипулятор. Вызов её обеспечивает перегруженная операция << в классе ostream.
6.8. Пользовательские манипуляторы с параметрами Пусть, например, необходимо создать новый манипулятор с параметрами wp(n,m), где n – ширина поля вывода, m – точность. Пример 6.8.1. //Класс манипулятора class my_manip { int n,m; ostream&(*f)(ostream&,int,int); public: //конструктор my_manip(ostream&(*F)(ostream&,int,int),int N, int M): f(F),n(N),m(M){} //здесь f,F – указатели на функцию friend ostream& operator<<(ostream& s,my_manip& my) {return my.f(s,my.n,my.m);} }; ostream& wi_pre(ostream& s,int n,int m) {s.width(n); s.flags(ios::fixed); s.precision(m); return s; } my_manip wp(int n,int m) {return my_manip(wi_pre,n,m); } void main() {cout<<52.3456<<endl<<wp(10,5)<<52.3456<<endl; } Рассмотрим этот пример подробнее. Конструктор класса my_manip имеет первый аргумент ostream&(*F)(ostream&,int,int). Здесь F – указатель на функцию, имеющую три аргумента типов ostream&, int и int и возвращающую ссылку на класс ostream. Теперь конкретную функцию можно задавать для класса my_manip через указатель при конструировании объекта этого класса. Мы задали функцию wi_pre. При выполнении cout<<wp(10,5) фактически вызывается следующая функция: osteram& operator<<(cout,wp(10,5)) {return wi_pre(cout,10,5);} Действительно, функция wp возвращает объект класса my_manip с передачей в его конструктор параметров: wi_pre, n и m. Сформулируем последовательность создания пользовательского манипулятора с параметрами. 1. Определить класс (my_manip) с полями: параметры манипулятора + поле – указатель на функцию типа ostream& (*f)(ostream&,<параметры манипулятора>); 2. Определить конструктор этого класса (my_manip) с инициализацией полей. 3. Определить в этом классе дружественную функцию – operator<<. Эта функция в качестве правого аргумента принимает объект класса (my_manip), левого аргумента (операнда) поток ostream и возвращает поток ostream как результат выполнения функции f. 4. Определить функцию типа *f(wi_pre), принимающую поток и параметры манипулятора и возвращающую поток. Эта функция выполняет форматирование.
5. Определить собственно манипулятор (wp) как функцию, принимающую параметры манипулятора и возвращающую объект my_manip, поле f которого содержит указатель на функцию wi_pre.
При выполнении манипулятора cout<<wp(10,5); вызывается функция wp, которая создает и возвращает объект my_manip, в поле f которого содержится указатель на функцию wi_pre. Затем вызывается функция operator<<, которой передается объект my_manip, созданный функцией wp. Наконец, функция operator<< выполняет функцию *f, т.е. wi_pre, и возвращает поток.
6.9. Использование макросов для создания манипуляторов Для создания манипуляторов можно использовать макросы OMANIP(int), IMANIP(int), IOMANIP(int). Эти макросы содержатся в файле <iomanip.h> Ниже приведены примеры использования этих макросов. а) манипулятор с одним параметром ostream& w(ostream& out,int n) {out.width(n); //out<<setw(n); return out; } OMANIP(int) w(int n) {return OMANIP(int)(w,n); } Здесь две функции с именем w, вторая на первый взгляд кажется ненужной. Однако OMANIP(int) – это макрос, позволяющий определить манипулятор, который принимает параметры. Обнаружив в операции << манипулятор, компилятор использует вторую функцию для вызова первой. б) манипулятор с двумя параметрами. Проблема здесь в том, что макросы принимают только один параметр, так что для передачи нескольких параметров придется использовать структуру или класс. Например, для двух параметров: struct Point { int n; int m; }; Перед использованием в манипуляторе Point нужно вызвать IOMANIPdeclare(Point). IOMANIPdeclare(Type) принимает только идентификатор, так что передача указателей или ссылок требует использования typedef. #include<iostream.h> #include<iomanip.h> struct Point {int x,y;}; IOMANIPdeclare(Point); ostream& wp(ostream& out,Point p) {out.width(p.x); out.flags(ios::fixed); out.precision(p.y); return out; } OMANIP(Point) wp(int x,int y) {Point p; p.x=x; p.y=y; return OMANIP(Point)(wp,p); }
Состояние потока Каждый поток имеет связанное с ним состояние. Состояния потока описываются в классе ios в виде перечисления enum. public: enum io_state { goodbit,//нет ошибки 0Х00 eofbit,//конец файла 0Х01 failbit,//последняя операция не выполнилась 0Х02 badbit,//попытка использования недопустимой операции 0Х04 hardfail //фатальная ошибка 0Х08 }; Флаги, определяющие результат последней операции с объектом ios, содержатся в переменной state. Получить значение этой переменной можно с помощью функции int rdstate();
Кроме того, проверить состояние потока можно следующими функциями: int bad(); возвращает 1, если badbit или hardfail int eof(); возвращает, если eofbit int fail(); возвращает, если failbit, badbit или hardfail int good();возвращает, если goodbit Если попытаться выполнить ввод из потока, который находится не в состоянии good(), то возвращается значение NULL. Если осуществляется чтение в переменную и происходит ошибка, то значение переменной не изменяется (предполагается, что переменная имеет стандартный тип, определенный в самом языке). Если операция >> используется для новых типов данных, то при её перегрузке необходимо предусмотреть соответствующие проверки. Установить состояние потока в заданное значение можно функцией void clear(int s=0); Эта функция устанавливает флаги ios::state в значение, заданное параметром s, не изменив при этом бита hardfail. Пример 10.1. #include<iostream.h> #include<stdlib.h> void main() {int flags; int k; cin>>k; flags=cin.rdstate(); if(flags) //если ошибка if(flags& ios:badbit) {cout<<”Ошибка\n”; cin.clear(0);} else{cerr<<”Эту ошибку нельзя исправить\n”; abort();} cout<<”Ввод без ошибок\n”; cout<<k<<endl; }
Пример 10.2. #include<iostream.h> #include<stdlib.h> void main() {int k; cin>>k; if(!cin) if(cin.bad()) {cout<<”Oшибка\n”; cin.clear(0);} else {cerr<<”Неисправимая ошибка\n”; abort();} cout<<”Ввод без ошибок\n”; cout<<k<<endl; } Здесь используется перегруженная int operator!(); Результат операции – значение функции ios::fail(). Есть еще operator void*(), который возвращает 0, когда fail() возвращает 1, в противном случае возвращает указатель this на объект ios.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|