Д.з. 1
Задача 1: order // Вспомогательная функция: // Упорядочиваем два числа void order(int& x, int& y) { if (x > y) { int tmp = x; x = y; y = tmp; } } void order(int& x, int& y, int& z) { order(x, y); order(x, z); order(y, z); } // Пример вызова: int a = 3, b = 1, c = 2; order(a, b, c); cout
Задача 2: Стек фиксированного размера 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
Задача 3: динамический стек 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) { // Нет места? int* newp = new int[maxsize*2]; // Копируем for (int i=0; i
Не забывайте про delete.. Если в классе где-то есть new, то, скорее всего, где-то должен быть и delete (в деструкторе?) И, скорее всего, delete должен быть столько же, cколько new См. также shared_ptr (позже..) 55
Задача 4: top class stack { … int & top() { return stk[size -1 ]; } … }; // Пример вызова: stack s; … s.top() = 5; s.top()++; 6
Задача 5: сравнение строк // Oдин из многих возможных способов решения.. bool compare(const char* s1, const char* s2) { while (*s1==*s2) { if (*s1=='\0') // Конец обеих строк? return true; s1++; s2++; } // Раз мы здесь, то строки не равны return false; } Типичная ошибка: нет const
Еще про константы 8
Константы и указатели - продолжение 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 цепляются друг за друга.
Наследование 12
Пример – время с секундами 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 ) { sec = 0; inc(); } } void time_with_sec::print() { time::print(); cout
Замечания 1. public наследование (другие почти не встречаются) 2. Слова: базовый класс / производный класс 3. Можем использовать поля и методы базового класса, как будто они наши собственные 4. Можем доопределять поля и методы (sec, inc_sec) 5. конструктор базового класса вызывается в списке инициализации 6. time::print() – указываем, что нас интересует именно метод базового класса 7. Самое главное – можно переопределять методы 8. Переопределяемые методы надо определять, как виртуальные (практически всегда) 14
protected поля и методы Такие методы доступны из производных классов. Например, м.б. лучше в time : class time { protected: int hour, min;// Можно использовать в time // и в производных классах … 15
Виртуальные функции (!!!) Правило: Если вы собираетесь переопределить функцию в производном классе, то в базовом классе вы должны описать ее, как виртуальную. class time { virtual void print(); … 16
Разница между виртуальными и не виртуальными функциями. Динамическое связывание. 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) 17
Почему это важно? void my_print(time& p) // а лучше, кстати, void my_print(const time& p { cout
Чисто виртуальные функции, абстрактные базовые классы 19
Пример: фигуры Рассмотрим набор классов для геометрических фигур: квадраты, ромбы, круги, треугольники и т.д. class shape { protected: int x, y; public: virtual void draw() = 0; shape(int x_, y_) : x(x_), y(y_) {} … еще методы (площадь, периметр и т.д.) … } ; 20
Чисто виртуальные функции В описании стоит =0; - функция называется чисто виртуальной (pure virtual). Это значит: Можно не задавать ее определения Ее обязательно надо переопределить в одном из производных классов. 21
Абстрактные базовые классы Если в классе есть хотя бы одна чисто виртуальная функция: Такой класс называется абстрактным (abstract base class) Нельзя создавать объекты абстрактного класса shape s;// Ошибка: нельзя создать об ъ ект // абстрактного класса Все это относится и к производным классам, если в базовом классе была чисто виртуальная функция, и ее еще не определили. 22
Исключения 23
Проблемы с обработкой ошибок: int pop() { return stk[--size]; //А если стек } // пуст?! // Вариант с проверкой if (size == 0) {... сообщить об ошибке … } return stk[--size]; 1. cout
Обработка ошибок с помощью исключений int stack::pop() { if ( size
Что можно бросать как исключение? Выражение может быть любого типа throw "Ошибка!"; throw 56; throw time(14, 30); Обычно используют специальные классы. throw my_exception(…какая-то информация об ошибке…); Есть стандартные классы (#include ) Например, throw std::bad_alloc(); Или throw std::exception("Стек пуст");
Подробнее о том, как ловить исключения (catch блоки) catch блоков может быть несколько try { … } catch (const char* s) { … обработка ошибок для строк … } catch (int i) { … обработка ошибок для int … } Порядок имеет значение! try { … } catch (time* p1) { … } catch (time_with_sec* p2) { … никогда не будет выполняться ! … } catch (…) - ловит все catch (…) { здесь обрабатываем все ошибки }
throw умеет выходить из нескольких функций int stack::pop() { if ( size
Что происходит в момент вызова throw? 1. Выходим из функций и блоков, в которых мы находимся … … пока не найдем try блок с подходящим нам catch блоком 2. После этого выполняется catch блок 3. (Если вообще не нашли подходящего try блока – аварийное завершение программы). 4. Важно: При выходе из функций и блоков вызываются все необходимые деструкторы
Более сложные возможности throw; void f() { try { … } catch (…) { … какая-то обработка … // Хотим снова бросить исключение для // дальнейшей обработки throw; } }
Еще про виртуальные функции. 31
Как это все реализовано? Таблица виртуальных методов (vtable). (здесь на занятии был рисунок на доске) При вызове виртуальной функции: Переходим по указателю на vtable В таблице берем нужный элемент (его номер известен при компиляции) Вызываем функцию Накладные расходы по памяти: + 1 слово в каждом обьекте + 1 таблица на класс 32
Константы в классах 33
Константные поля class abc { const int n; … abc::abc() : n(100)// Задавать можно только здесь { … abc::abc() { n = 100; // Тут уже задавать n нельзя Используются довольно редко.. 34
Константные методы class abc { int i; void f() const { i++;// ошибка } … Нельзя менять поля Из f можно вызывать только константные методы Зачем? Ловим ошибки в программе Смысл метода становится понятнее (удобнее пользоваться) Техническая причина: к константным объектам можно применять только такие методы const time lunch_time(12,50); lunch_time.print(); // Только если у print - // константный метод 35
Пример, когда с const все не так просто class strange_shape { … }; double strange_shape::area() { … полчаса вычислений… } Чтобы не считать каждый раз: strange_shape() : saved_area(0) {…{… double strange_shape::area() { if (saved area == 0) { saved_area = … полчаса вычислений… } return saved_area; } Прием: кеширование (cache) 36
Проблема… class strange_shape { … double strange_shape::area() const { … saved_area = …; // Изменение поля в константном методе??!! } … int saved_area; }; А как решается эта проблема - мы узнаем на следующем занятии… 37
Д.з. 38
Д.з a. Определить класс «равнобедренный треугольник» (с основанием, параллельным оси X). Для него определить конструктор и метод draw. (В методе draw вместо рисования можно просто печатать координаты отрезков). б. Определить какой-нибудь класс, производный от класса «равнобедренный треугольник» (например, «заштрихованный вертикально равнобедренный треугольник» или «зашьтрихованный горизонтально» или «треугольник с медианами» и т.д.) 2. Написать абстрактный класс shape и какие-нибудь два производных от него класса (Например, треугольник и ромб или прямоугольник или круг и т.д.). Для этих классов определить конструкторы и функции- методы area (площадь) и perim (периметр). (Метод draw в этой задаче определять не надо). 39
Д.з Определить структуру (или класс) «односвязный список». а. Ввести число n и создать список из чисел n, n-1, n-2, … 3, 2, 1. б. Напечатать все числа в списке. (Если не очень понятно, как это делать или о чем вообще речь – на сайте есть документ list.doc с пояснениями и подказками.) 4.Проверять в конструкторе time корректность параметров. (Например, time t(25, 1); - параметры неправильные). Об ошибке сообщать с помощью исключения. Привести пример обработки этого исключения (Т.е. попытаться создать «неправильный» объект, и поймать возникшее исключение.) 40
Д.з Пусть есть функция: void f(stack& s) { int* p = new int[1000]; // … тут, допустим, мы что-то делаем с p и s cout