370 likes | 504 Views
Д.з. 1. Задача 1 : Стек фиксированного размера. class stack { int stk[100]; int size; public: stack() : size(0) {} void push(int i) { stk[size++] = i; }. int pop() { return stk[--size]; } }; stack s; s.push(5); s.push(10); s.push(2); cout << s.pop();
E N D
Д.з. 1
Задача 1: Стек фиксированного размера class stack { int stk[100];int size; public: stack() : size(0) {}void push(int i){ stk[size++] = i;} int pop(){ return stk[--size];} }; stack s; s.push(5); s.push(10); s.push(2); cout << s.pop(); int i = s.pop() – s.pop(); // В последней строчке // результат не определен! 2
Задача 2: динамический стек class dynstack { int size;// Количество элементов int maxsize;// Макс. количество int* p; // Отведенная память public: dynstack() : size(0), maxsize(10) { p = new int[maxsize]; } ~dynstack() { delete [] p; // Многие забыли.. } void push(int val) { if (size == maxsize) {// Нет места? auto newp = new int[maxsize*2]; // Копируем for (int i=0; i<size; i++) { newp[i] = p[i]; } maxsize *= 2; delete [] p; p = newp; // Переставляем } // указатель p[size++] = val; } int pop() { return p[--size]; } }; 3 3
Не забывайте про delete.. • Если в классе где-то есть new, то, скорее всего, где-то должен быть и delete (в деструкторе?) • И, скорее всего, delete должно быть столько же, cколько new • Но в С++11 часто вместо деструкторов используют умные указатели (unique_ptr, shared_ptr) – об этомпозже… 4 4
class stack { … int & top() { return stk[size-1]; } … }; // Пример вызова: stack s; … s.top() = 5; s.top()++; Задача 3: top 5
Задача 5: сравнение строк // Oдин из многих возможных способов решения.. bool compare(const char* s1, const char* s2) { while (*s1==*s2) { if (*s1=='\0') // Конец обеих строк? returntrue; s1++; s2++; } // Раз мы здесь, то строки не равны return false; } • Типичная ошибка: нет const
Иногда пишут: void f(const int i){…} Замечание Нет особого смысла..
Константы и указатели - продолжение • const int* p;означает: «Обещаем не менять *p с помощью p». int i; const int* p = &i;// ОК? *p = 5; // Так ошибка i = 5; // А так? // OK! • А так можно писать? const int i = 100; int* p = &i; Нет! (Иначе *p = 5; - плохо, испортим константу) На константные переменные могут указывать только constуказатели. 9
Замечание про константы и указатели в параметрах функций const int a[] = {3, 7, 11};void myfunc1(int* p){ …}myfunc1(a); // Ошибка // Авдруг вы в myfunc1 поменяете массив ?! • Если объекты константные • то параметры функции, которая с ними работает тоже д.б. const(если это - указатель или ссылка) 10
Замечания • Если не писать правильно const в параметрах • товаши функции м.б. потом будет сложно вызывать То есть • const лучше сразу писать правильно • трудно добавлять понемногу / по частям..) const ‘цепляются’ друг за друга.
sec = 0; inc(); } } void time_with_sec::print() { time::print(); cout << "." << sec; } // использование time_with_sec time_with_sec ts(12, 30, 45); ts.inc(); ts.inc_sec(); ts.print(); Пример – время с секундами class time_with_sec : public time { int sec; public: time_with_sec(int h, int m, int s); void inc_sec(); void print(); }; time_with_sec::time_with_sec(int h,int m,int s): time(h, m), sec(s) {} void time_with_sec::inc_sec() { sec++; if ( sec == 60 ) {
Замечания • public наследование (другие почти не встречаются) • Слова: базовый класс / производный класс • Можем использовать поля и методы базового класса, как будто они наши собственные • Можем доопределять поля и методы (sec, inc_sec) • конструктор базового класса вызывается в списке инициализации • time::print() – указываем, что нас интересует именно метод базового класса • Самое главное – можно переопределять методы • Переопределяемые методы надо определять, как виртуальные (практически всегда)
protected поля и методы Такие методы доступны из производных классов. Например, м.б. лучше в time :class time {protected: int hour, min;// Можно использовать в time // и в производных классах …
Виртуальные функции (!!!) • Правило: Если вы собираетесь переопределить функцию в производном классе, то в базовом классе вы должны описать ее, как виртуальную. class time {virtual void print(); …
Разница между виртуальными и не виртуальными функциями. Динамическое связывание. time* p;if ( …какое-то условие… ) {p = new time(8, 50);} else { p = new time_with_sec (11, 30, 15);} // на что указывает p ?? // - неизвестно.. p->print(); // Какой print// будет вызван? • Если функция виртуальная, то будет вызываться «правильный» print: time::print() илиtime_with_sect::print()в зависимости от типа p • Называется: динамическое связывание (dynamic binding) или позднее связывание (late binding)
Почему это важно? void my_print(time& p) // а лучше, кстати, void my_print(const time& p) { cout << "Московское время:"; p.print();} time t1(8, 50);time_with_sec t2(11, 30, 15); my_print(t1);my_print(t2); Будут вызваны разные print ! • Виртуальные функции позволяют писать код, который умеет работать с разными типами! Называется: полиморфизм
Чисто виртуальные функции, абстрактные базовые классы
Пример: фигуры Рассмотрим набор классов для геометрических фигур: квадраты, ромбы, круги, треугольники и т.д. class shape { protected: int x, y; public: virtual void draw() = 0; shape(int x_, y_) : x(x_), y(y_) {} … еще методы (площадь, периметр и т.д.) …};
Чисто виртуальные функции • В описании стоит =0;- функция называется чисто виртуальной (pure virtual). Это значит: • Можно не задавать ее определения • Ее обязательно надопереопределить в одном из производных классов.
Абстрактные базовые классы • Если в классе есть хотя бы одна чисто виртуальная функция: • Такой класс называется абстрактным (abstract base class) • Нельзя создавать объекты абстрактного класса shape s; // Ошибка: нельзя создать объект // абстрактного класса • Все это относится и к производным классам, если в базовом классе была чисто виртуальная функция, и ее еще не определили.
int pop() { return stk[--size];//А если стек }// пуст?! // Вариант с проверкойif (size == 0) {... сообщить об ошибке …}return stk[--size]; cout << "Oшибка!"; return 0; // Взводим глобальную переменнуюerr = true; Еще один параметр Еще проблемы: Как сообщать об ошибках в конструкторах? Хорошо бы поменьше проверок.. Проблемы с обработкой ошибок:
Обработка ошибок с помощью исключений int stack::pop() { if ( size<= 0 ) throw "Стек пуст"; return stk[--size]; } // Потом где-то в другом месте: try { … s.pop(); … } catch ( char * s ) { cout << “Oшибка: " << s; } • throw выражение; - сообщить об ошибке • try { операторы (try блок)}catch (тип переменная) { операторы обработки ошибки (catch блок)}
Что можно бросать как исключение? • Выражение может быть любого типа • throw "Ошибка!"; • throw 56; • throwtime(14, 30); • Обычно используют специальные классы. • throw my_exception(…какая-то информация об ошибке…); • Есть стандартные классы (#include <stdexcept>) • Например, throw std::bad_alloc(); • Или throw std::exception("Стек пуст");
catch блоков может быть несколько try { … } catch (const char* s) {… обработка ошибок для строк …}catch (int i) { … обработка ошибок для int …} Порядок имеет значение!try { … } catch (time* p1) {… }catch (time_with_sec* p2) { … никогда не будет выполняться ! …} catch (…) - ловит все catch (…) { здесь обрабатываем все ошибки } Подробнее о том, как ловить исключения (catch блоки)
throw умеет выходить из нескольких функций int stack::pop() { if ( size<= 0 ) throw "Стек пуст";… } void f() { …stack s;…s.pop();… } void g() { …f(); … } try { … g(); … } catch ( char * s ) { cout << “Oшибка: " << s; } 28
Что происходит в момент вызова throw? • Выходим из функций и блоков, в которых мы находимся … • … пока не найдем try блок с подходящим нам catch блоком • После этого выполняется catch блок • (Если вообще не нашли подходящего try блока – аварийное завершение программы). • Важно:При выходе из функций и блоков вызываются все необходимые деструкторы
Более сложные возможности • throw; void f() { try { … } catch (…) { … какая-то обработка … // Хотим снова бросить исключение для // дальнейшей обработки throw; } }
Константные поля class abc { const int n; … abc::abc() : n(100)// Задавать можно только здесь { … abc::abc() { n = 100; // Тут уже задавать n нельзя Используются довольно редко.. 32
Константные методы class abc { int i; void f() const { i++; // ошибка } … Нельзя менять поля Из f можно вызывать только константные методы Зачем? Ловим ошибки в программе Смысл метода становится понятнее (удобнее пользоваться) Техническая причина: к константным объектам можно применять только такие методы const time lunch_time(12,50); lunch_time.print(); // Только если у print - // константный метод 33
Д.з.-1 • a. Определить класс «равнобедренный треугольник» (с основанием, параллельным оси X). Для него определить конструктор и метод draw. (В методе draw вместо рисования можно просто печатать координаты отрезков).б. Определить какой-нибудь класс, производный от класса «равнобедренный треугольник»(например, «заштрихованный вертикально равнобедренный треугольник» или «зашьтрихованный горизонтально» или «треугольник с медианами» и т.д.) • Написать абстрактный класс shape и какие-нибудь два производных от него класса (Например, треугольник и ромб или прямоугольник или круг и т.д.). Для этих классов определить конструкторы и функции- методы area (площадь) и perim (периметр). (Метод draw в этой задаче определять не надо).
Д.з.-2 • Определить структуру (или класс) «односвязный список». а. Ввести число n и создать список из чисел n, n-1, n-2, … 3, 2, 1.б. Напечатать все числа в списке.(Если не очень понятно, как это делать или о чем вообще речь – на сайте есть документ list.doc с пояснениями и подказками.) • Проверять в конструкторе time корректность параметров. (Например, time t(25, 1); -параметры неправильные). • Об ошибке сообщать с помощью исключения. • Привести пример обработки этого исключения (Т.е. попытаться создать «неправильный» объект, и поймать возникшее исключение.)
Д.з. - 3 • Пусть есть функция:void f(stack& s){int* p = new int[1000]; // … тут, допустим, мы что-то делаем с p и scout << s.pop(); // … а тут еще что-то делаем с p и s delete [] p; // Потенциальная утечка памяти: delete может и не вызваться! } При выполнении может происходить утечка памяти (если в pop() вызывается исключение). Изменить функцию, чтобы выполнялиcьте же действия, в том же порядке (new pop delete), но чтобы при исключении память не терялась 37