
#6 Иерархии классов.pptx
- Количество слайдов: 23
Иерархии классов Степанюк Константин Сергеевич 24 const@gmail. com
class Char. List { public: 1. Char. List(int size); ~Char. List(); 2. Char. List & operator = (const Char. List & other); private: char* data; 3. }; Char. List: : Char. List(int size) { data = new char[256]; } 4. Char. List: : ~Char. List() { if (data != NULL) { delete data; 5. } } Char. List& Char. List: : operator = (const Char. List & other) { int size = std: : strlen(other. data); data = new char[size]; std: : copy(other. data, other. data + size, data); return *this; } Фиксированное число 256 вместо переменной size Проверка на NULL перед оператором delete Удаление объекта (delete) вместо удаления массива (delete[]) Отсутствие проверки на самоприсваивание При копировании не освобождается «старая» память
Char. List: : Char. List(int size) { data = new char[size]; } Char. List: : ~Char. List() { delete[] data; } Char. List& Char. List: : operator = (const Char. List & other) { if (this != &other) { // защита от неправильного самоприсваивания // 1: освобождаем "старую" память delete [] data; // 2. ищем размер строки int size = std: : strlen(other. data); // 3: выделяем "новую" память и копируем элементы data = new char[size]; std: : copy(other. data, other. data + size, data); } } // по соглашению всегда возвращаем *this return *this;
Традиционный подход (стиль C) Проблема –автоматизация предприятия, информация о сотрудниках struct Employee { string first. Name, family. Name; Date hiring. Date; short department; //… }; struct Manager { Employee emp; //Информация о менеджере как сотруднике set
Проблемы традиционного подхода • Концептуально Менеджер является сотрудником, но с точки зрения компилятора между Employee и Manager нет общности • Manager* не является Employee*, нельзя использовать Manager* там где используется Employee* • Manager* нельзя напрямую положить в список или множество объектов Employee* • Решение –механизм наследования с возможностью явного выражения общности между абстракциями
Стиль С++ -наследование struct Manager : public Employee { set
Использование –основные концепции • Наследование – основной механизм ООП: – Уточнение (переопределение поведения –перегрузка) – Расширение (интерфейса абстракции) – Переиспользование (использование ранее написанного кода, иногда, в данном случае, лучше воспользоваться агрегацией) • Уточнение и расширение – базис в направлениях которого идут изменения в классах • LSP(Liskov substitution principle): функция, принимающая в качестве аргумента объект базового класса должна уметь работать с объектами любых открытых производных классов
Уточнение - пример class Employee { //…. public: string full. Name() { return first. Name+ “ “+last_name; } void print( ostream& ostr) { ostr<<“Name: ”<
Расширение –пример class Manager: public Employee { set
Виртуальный полиморфизм – постановка • Проблема – есть список сотрудников, среди которых есть менеджеры. Необходимо распечатать информацию о каждом сотруднике с учетом того, что он может быть менеджером //Напрямую –не работает list
Альтернативы • Гарантировать что список содержит только элементы одного типа (Employee* либо Manager*) – трудно достичь • Поместить поле о типе (поле типа) в базовый класс, чтобы заинтересованные функции могли его проверять enum Emp. Type {E, M}; //Что делать если потребуется расширение? class Employee { Emp. Type employee. Type; public: Emp. Type get. Type () {return employee. Type; } }; • Использовать динамическое преобразование типа dynamic_cast
Виртуальный полиморфизм – решение class Employee { //… public: virtual void print(ostr& os) {…} }; class Manager : public Employee{ //… public: virtual void print(ostr& os) {…} }; list
Реализация operator<< ostream& operator<< (ostream& o, const Employee &emp) { emp. print( o ); return o; } list
Использование виртуальных функций • В случае адресации объекта по ссылке или указателю осуществляется вызов виртуальной функции соответствующей реальному типу объекта • В случае использования обычных переменных происходит усечение при преобразовании типа и будет осуществлен вызов функции, соответствующей типу переменной Manager *mp = …; Manager m = …; Employee *pemp = mp; Employee &remp = m; Employee emp = m; pemp->print(std: : cout); remp. print(std: : cout); //Объект имеет тип Manager //Внимание –усечение!!! // Вызов Manager: : print // Вызов Employee: : print
Таблица виртуальных функций • Таблица виртуальных функций служит для реализации виртуального полиморфизма и содержит указатели на виртуальные функции
Виртуальные функции и конструкторы • При вызове конструктора или деструктора базового класса при создании (уничтожении) объекта производного класса указатель на таблицу виртуальных функций указывает на таблицу этого базового класса • Таким образом виртуальные функции не рассматриваются как виртуальные при вызове из конструкторов или деструкторов (ведут себя как обычные функции объявленные в классе) • Избегайте вызова виртуальных функций в конструкторах и деструкторах
Виртуальные конструкторы Виртуальных конструкторов нет Эквивалентного поведения можно добиться с помощью виртуальных функций для создания объектов class Base { public: virtual Base* clone () const { return new Base(*this); } }; class Derived: public Base { public: //Именно Derived*!, компилятор учитывает, что Derived //наследник Base при такой перегрузке виртуальной ф-ии virtual Derived* clone () const { return new Derived(*this); } }; • •
Базовый класс class Base { A field, *pointer; public: Base(); //Конструктор по умолчанию Base(const Base&); //Конструктор копии Base& operator= (const Base&); virtual ~Base(); //Виртуальный деструктор!!! }; Base: : Base(): field(0){ pointer = new A(); } Base: : Base(const Base& m): field(m. field) { pointer = new A(*(m. pointer)); } Base: : ~Base() { delete pointer; }
Базовый класс const Base& Base: : operator = (const Base& m) { //Проверка на присваивание самому себе if (this != &m) { delete pointer; //Высвобождение указателя field = m. field; pointer = new A(*(m. pointer)); } return *this; } const Base& Base: : operator = (const Base& m) { if (this != &m) { field = m. field; (*pointer) = *(m. pointer); } return *this; }
Производный класс class Derived : public Base { Cls *pc; public: Derived(); Derived(const Derived&); virtual ~Derived(); //Виртуальный! const Derived& operator = (cosnt Derived&); }; Derived: : Derived(): Base() { pc = new Cls(); } Derived: : Derived(const Derived &r): Base(r) { pc = new Cls(*(r. pc)); }
Производный класс const Derived& Derived: : operator=(const Derived &r) { if (this != &r) { *( static_cast
Управление доступом • Спецификатор доступа к базовому классу управляет доступом к членам базового класса и возможностью преобразования объектов, указателей и ссылок из типа производного класса в тип базового класса class X: public B {…} class Y: protected B{…} class Z: private B{…} • Закрытого наследования следует избегать, вместо него в подавляющем большинстве случаев лучше воспользоваться агрегацией