Курс ООП в С презентация. ООП в с++(полный курс). Объекты и классы
Скачать 1.76 Mb.
|
Потоковые классыПотоки и типы, определяемые пользователем Для ввода и вывода в потоках используются перегруженные для всех стандартных типов операции чтения и извлечения << и >>. При этом выбор конкретной операции определяется типом фактических параметров. Иногда разработчику требуется единый способ определения состояния объектов. Под этим подразумевается ввод и вывод объектов с помощью соответствующих потоков. Для организации подобного интерфейса с пользователем необходимо определить операции чтения и извлечения в том классе, объекты которого будут передаваться потокам. Потоковые классыclass person { public: ... friend istream& operator>>(istream& i, person& rhs); friend ostream& operator<<(ostream& o, const person& rhs); ... private: string name; string sirname; int year; int weight; }; istream& operator>>(istream& i, person& rhs) { return i >> rhs.name >> rhs.sirname; } ostream &operator<<(ostream& o, const person& rhs) { return o << rhs.name << " " << rhs.sirname << endl; } Форматирование при вводе/выводе В потоковых классах форматирование выполняется тремя способами:
манипуляторов; форматирующих методов. Флаги представляют собой отдельные биты, объединенные в поле x_flags класса ios. Ввод/вывод в языке С++ средствами стандартной библиотекиТаблица 3. Флаги форматирования потоковых классов
Флаги [left, right и internal], [dec, oct и hex], а также [scientific и fixed] взаимно исключают друг друга, т.е. в каждый момент может быть установлен только один флаг из каждой группы. Методы управления флагами класса ios: Все функции возвращают прежние флаги потока
В классе ios определены методы форматирования: … которые используют следующие защищенные атрибуты целого типа:
Манипуляторы – это функции, которые можно включать в цепочку операций помещения и извлечения для форматирования данных, т. е. их можно включать прямо в поток. Манипуляторы делятся на простые, не требующие указания аргументов, и параметризованные. Простые манипуляторы:
Параметризированные манипуляторы требуют указания аргумента. Для их использования к программе надо подключить заголовочный файл
В качестве ещё одного варианта реализации интерфейса с человеком приведём такой элемент диалога как таблица. class person_util { public: static int start_x; static void make_title(); static void make_table(person&); }; Метод make_table(), кроме возможностей библиотеки "windows.h", иллюстрирует механизмы использования потоков и их манипуляторов. Задача этого метода заключается в последовательном выводе колонок с информацией о значении атрибутов объекта типа person. Для этого из структуры CSBI извлекаются текущие координаты курсора, и в соответствующей горизонтальной позиции выводится строка необходимой ширины, имеющая нужный цвет и выравнивание. Атрибут цвета текста (attribute) определяется побитовым сложением числовых констант: FOREGROUND_RED | FOREGROUND_GREEN| FOREGROUND_INTENSITY| BACKGROUND_BLUE, что, в итоге, представляется десятичным числом 30 и желтыми символами на синем фоне. void person_util::make_table(person& arg) { HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO CSBI; COORD c; GetConsoleScreenBufferInfo(hStdout, &CSBI); c = CSBI.dwCursorPosition; c.X = start_x; WORD attribute = 30; SetConsoleCursorPosition(hStdout, c); SetConsoleTextAttribute(hStdout, attribute); cout.setf(ios::left); cout.width(20); cout << arg.get_name(); SetConsoleTextAttribute(hStdout, 7); cout << " "; SetConsoleTextAttribute(hStdout, attribute); cout << setw(20) << arg.get_sirname(); SetConsoleTextAttribute(hStdout, 7); cout << endl; } Создание заголовка таблицы методом make_title() мы не приводим, поскольку он представляет собой упрощенный вариант рассмотренного выше make_table(). Напомним лишь, что нашему статическому атрибуту надо присвоить начальное значение: int person_util::start_x = 0; ставим флаг "выравнивание налево": используем метод форматирования ширины поля вывода: функция-манипулятор тоже определяет ширину: Пользователь может создать свой собственный манипулятор и определить в нем правила, по которым будет осуществляться вывод информации в поток typedef ostream&(*point)(ostream&, int, char);//point - тип-указатель //на функцию-манипулятор: class mnp //класс для манипулятора { int width; //ширина поля вывода char fill; //символ-заполнитель point fun; //функция, определяющая ширину и заполнитель public: mnp(int w, char c, point f) : width(w), fill(c), fun(f) {} //перегруженная функция-операция для вывода объекта в поток: friend ostream& operator<<(ostream&, mnp); }; ostream& operator<<(ostream& out, mnp m) { return m.fun(out, m.width, m.fill);//возвращаем поток как результат //работы функции-манипулятора: } ostream& format(ostream& out, int w, char f) { //выполняем форматирование для выводимых данных: out.width(w); //ширина out.fill(f); //заполнитель //правое выравнивание для всех данных, //вывод целых - в шестнадцатиричной СС и верхнем регистре: out.flags(ios::right|ios::uppercase|ios::hex); return out; } void main() { //выводим A на 15 позициях, с заполнением пустоты точками: int A = 987; cout << mnp(15, '.', format) << A << endl; } В данном случае для объекта класса mnp вызывается перегруженный оператор <<, который обращается к функции format() через атрибут-указатель fun объекта m, передавая ей основные параметры формата вывода. С тем же успехом, вместо конструктора класса, можно было бы вызвать функцию вида: mnp set_stream(int w, char f) { return mnp(w, f, format); } …подменяющую явное обращение к объекту класса mnp. В любом случае, пользователь увидел бы что-нибудь наподобие "............3DB". Методы обмена с потоками Для работы с объектами потоковых классов наряду с операциями (>> и <<) определены ещё и методы неформатированного чтения и записи в поток. Преобразование данных при этом не выполняется.
При организации диалогов с пользователем программы при помощи потоков необходимо учитывать буферизацию. Например, при выводе приглашения к вводу мы не можем гарантировать, что оно появится раньше, чем будут считаны данные из входного потока, поскольку приглашение появится на экране только при заполнении буфера вывода. Для решения этой проблемы определена функция tie(), которая связывает потоки istream и ostream с помощью вызова вида cin.tie(&cout). После этого вывод очищается (т. е. выполняется функция cout.flush()) каждый раз, когда требуется новый символ из потока ввода. Приведем пример использования некоторых методов: cin.ignore(7, ','); streambuf SB = *cin.rdbuf(); cout << &SB; Очистив поток ввода, мы получаем от пользователя символы, которые заносятся в поток при определенных условиях: когда пропущено 7 символов, или после того, как была встречена запятая. Далее инициализируем собственный буфер буфером потока cin, и передаем его потоку cout. Таким образом, для строки "123,4567890" получим результат "4567890", а для "12345678,90" уже "8,90"! Ошибки потоков Наиболее распространенная ошибка при использовании потоков – не заметить, что введено не то, что предполагалось, поскольку на входе был не тот формат. Нужно или проверять состояние потока ввода перед использованием предположительно считанных данных, или задействовать исключения. Для отслеживания ошибок потоков в базовом классе ios определено поле state, отдельные биты которого отображают состояние потока.
Получить текущее состояние потока можно с помощью метода int rdstate(), но существуют и другие более удобные методы. Если произошла ошибка ввода, в результате которой установлен флаг только failbit, то работу с потоком можно продолжить, но сначала надо сбросить флаги ошибок с помощью метода clear(). Этот же метод используется и для установки флагов, если передать ему не нулевой аргумент.
Stream.clear(); //сбросить все флаги Stream.clear(rdstate() | ios::eofbit);// установить флаг конец файла Stream.clear(ios::eofbit); // установить флаг конец файла. //и сбросить все остальные while(Stream) { //ввод или вывод … }; Файловые потоки Файл – именованная информация на внешнем носителе, т. е. логически файл – это конечное множество последовательных байтов, следовательно, устройства, такие как принтер, клавиатура и монитор, являются частным случаем файлов. По способу доступа файлы можно разделить на
файлы с произвольным доступом, допускающие чтение и запись в указанную позицию. Стандартная библиотека содержит три класса для работы с файлами: ifstream – класс входных файловых потоков; ofstream – класс выходных файловых потоков; fstream – класс двунаправленных файловых потоков. Эти классы являются производными от классов istream, ostream и iostream соответственно, поэтому они наследуют перегруженные операции << и >>, флаги форматирования, манипуляторы, методы, состояние потоков и т. д. Использование файлов в программе предполагает следующие операции: создание потока; открытие потока и связывание его с файлом; обмен (ввод/вывод); уничтожение потока; закрытие файла. Конструкторы без параметров создают объект соответствующего класса, не связывая его с файлом. Конструкторы с параметрами создают объект соответствующего класса, открывают файл с указанным именем и связывают файл с объектом: ifstream(const char *name, int mode = ios::in); ofstream(const char *name, int mode = ios::out | ios::trunc); fstream(const char *name, int mode = ios::in | ios::out); Вторым параметром является режим открытия файла. Вместо значения по умолчанию можно указать одно из следующих значений, определенных в классе ios: По умолчанию файлы открываются в текстовом режиме. Это означает, что на вводе последовательность возврата каретки/перевода строки преобразуется в символ '\n'. На выводе символ '\n' преобразуется в последовательность возврат каретки/перевод строки. В двоичном режиме такие преобразования не выполняются. Функция компонента ofstream::open объявляется следующим образом: void open(char * name, int=ios::out, int prot=filуbuf::openprot); Аналогично, объявление ifstream::open имеет вид: void open(char * name, int=ios::in, int prot=filуbuf::openprot); Второй аргумент, называемый режимом открытия, имеет показанные умолчания. Аргумент режима открытия (возможно, связанный операцией ИЛИ с несколькими битами режима) можно явно задать в следующей форме:
ios::out – открыть файл для записи; ios::ate – установить указатель на конец файла, читать нельзя, можно только записывать данные в конец файла; ios::app – открыть файл для добавления; ios::trunc – если файл существует, то создать новый; ios::binary – открыть в двоичном режиме; ios::nocreate – если файл не существует, выдать ошибку, новый файл не открывать; ios::noreplace – если файл существует, выдать ошибку, существующий файл не открывать; Открыть файл в программе можно с использованием либо конструкторов, либо метода open, имеющего такие же параметры, как и в соответствующем конструкторе. fstream stream; //конструктор без параметров //открывается файл, который связывается через fbuf с потоком stream.open("..\\f.dat",ios::in); //конструктор с параметрами, создает и открывает для чтения файл fstream stream("..\\f.dat",ios::in); После того как файловый поток открыт, работать с ним можно также как и со стандартными потоками cin и cout. При чтении данных из входного файла надо контролировать был ли достигнут конец файла после очередной операции вывода. Это можно делать с помощью метода eof(). Если в процессе работы возникнет ошибочная ситуация, то потоковый объект принимает значение равное 0. Когда программа покидает область видимости потокового объекта, то он уничтожается, при этом перестает существовать связь между потоковым объектом и физическим файлом, а сам файл закрывается. Если файл требуется закрыть раньше, то используется метод close(). //вспомогательная функция int GetInt(istream&in) //ввод целого числа из потока in { int value; while(1) //бесконечный цикл { in>>value; //чтение целого числа if(in.peek=='\n')//если в буфере типа streambuf, связанном с //потоком in находится изображение целого числа, заканчивающегося //'\n', то после извлечения этого числа в буфере останется только //'\n'. Выполняется проверка этого условия. { in.get(); //буфер очищается break; //выход, т. к. число введено верно } else { cout<<"\nError in data; Repeat, please:"< while(in.get()!='\n'{}; //из буфера извлекаются все } //символы до символа '\n' } return value; } //функция чтения объекта из потока istream& operator >>(istream& in, person& S) { in.getline(S.name,20); // читает из потока символы, пока не //встретится символ '\n' или пока не будет прочитано 19 символов, //прочитанные символы помещаются в массив S.name и к ним //добавляется '\0'. S.age=GetInt(in); //вызов функции для ввода целого числа return in; } Одним из типовых средств работы пользователя с данными программы является меню, под которым, кроме экранной формы, подразумевается и некоторый объект системы. Класс для него должен описывать характеристики экранного объекта и методы для работы с ним. class menu { public: void add_string(string s); //добавить строку в меню int show(); //показать меню menu(int, int, int, int); //конструктор с параметрами void redraw(); //перерисовка экранной формы private: int pos; //начальная позиция отображения int curr_pos; //текущая позиция маркера меню int start_y; //начальная координата Y int start_x; //начальная координата X unsigned int height; //высота в строках unsigned int width; //ширина в символах vector HANDLE hStdout; }; Исходя из назначения данного класса, единственным конструктором, имеющим смысл, является конструктор с параметрами, задающий значения экранных координат, ширину и высоту меню, а также определяющий контекст. menu::menu(int sx, int sy, int w, int h) { start_x = sx; start_y = sy; width = w; height = h; pos = 0; curr_pos = 0; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); } Поскольку пользователь осуществляет взаимодействие с меню на основе символьной информации, меню должно содержать массив составляющих его строк, что реализуется посредством включения в класс меню атрибута Strings контейнерного типа vector void menu::add_string(string s) { int count = width - s.size(); if (count) s.append(count, ' '); else s = s.substr(0, width); Strings.push_back(s); } Метод redraw()предназначен для перерисовки меню при изменении его состояния, которое определяется номером pos первой отображаемой строки и позицией curr_pos маркера внутри строк, выводимых на экран. Эти значения непосредственно зависят от действий пользователя, обрабатываемых методом show(). void menu::redraw() { COORD c; c.X = start_x; c.Y = start_y; int y = 0; WORD attribute; vector for(int i = curr_pos; i < curr_pos+height && si != Strings.end(); i++, si++, y++) { c.Y = start_y + y; SetConsoleCursorPosition(hStdout, c); if(i == pos) { attribute = FOREGROUND_RED|FOREGROUND_GREEN | FOREGROUND_INTENSITY|BACKGROUND_BLUE|BACKGROUND_INTENSITY; } else { attribute = FOREGROUND_RED|FOREGROUND_GREEN| FOREGROUND_INTENSITY|BACKGROUND_BLUE; }; SetConsoleTextAttribute(hStdout,attribute);cout << Strings[i]; SetConsoleTextAttribute(hStdout, 0); cout << endl; } } Метод show() непосредственно реализует интерфейс с пользователем, изменяя состояние меню в зависимости от нажатия клавиш человеком, и возвращая по нажатию клавиши Enter номер выбранной строки. int menu::show() { int z; if (Strings.begin() != Strings.end()) { redraw(); do { z = _getch(); //получаем код нажатой клавиши switch (z) { case 72: //нажата клавиша "стрелка вверх" { SetConsoleTextAttribute(hStdout, 0); if (pos == curr_pos && pos > 0) --curr_pos; if (pos > curr_pos) --pos; break; } case 80: //нажата клавиша "стрелка вниз" { SetConsoleTextAttribute(hStdout, 120); if (pos < Strings.size()-1) ++pos; if (pos == curr_pos+height && pos < Strings.size()) ++curr_pos; break; } } redraw(); } while (z != 13); //выполняем, пока не нажали Enter SetConsoleTextAttribute(hStdout, 7); return pos; } else { SetConsoleTextAttribute(hStdout, 7); return -1; } } Нижеследующий пример показывает создание меню на основе массива объектов класса person, имена и фамилии которых передаются объекту-меню M в качестве строк. void main() { person x; string s; int index; vector Persons; vector ::iterator pi; for (int i=0; i < 2; i++) { cout << "Enter name: "; cin >> s; x.set_name(s); cout << "Enter sirname: "; cin >> s; x.set_sirname(s); Persons.push_back(x); } menu M(10, 10, 15, 4); for (pi = Persons.begin(); pi != Persons.end(); pi++) { M.add_string(pi->get_name()+" "+pi->get_sirname()); } index = M.show(); cout << Persons[index].get_sirname() << endl; cout << "Address of object is " << &Persons[index] << endl; } Поскольку метод show() возвращает индекс элемента меню, можно узнать, и какой объект был выбран, и какой у этого объекта адрес, о чём и говорят две последние строки кода. Теперь мы можем связать объект или указатель на него с любым другим объектом, передавая их как параметры соответствующим методам этих объектов. Отметим, что использование меню, естественно, не ограничивается выбором единственного объекта, и может быть продолжено путём очередных обращений к методу show(). Контейнерные классы |