Яп
.pdf2/27/2023
Одиночное и множественное наследование |
Множественное наследование |
•Наследование бывает одиночным и множественным.
•Одиночное– у потомка есть единственный родитель.
•Множественное– у потомка два и более родителей.
362
Проблемы множественного наследования
1. Конфликтыименмеждуразличнымисуперклассами.В
двух и болеесуперклассахопределено полеили операция с одинаковымименем.
2. Повторное наследование – класснаследует двумклассам, а те в свою очередь порозньнаследуют одномуи томуже четвертому.
В подклассе появятся копии полей и функций из обоих классов и потребуется явное указание происхождения каждой копии.
363 |
364 |
91
classComputer { private:
void turn_on(){ cout << "Computer is on." << endl; }
};
classMonitor{ public:
void turn_on(){ cout << "Monitor is on." << endl; }
};
classLaptop:public Computer, public Monitor {};
int main()
{
LaptopLaptop_instance;
//Laptop_instance.turn_on();
//will causecompile time error return0;
}
Разрешить неоднозначное наследование изменением уровня доступа к данным на приватный невозможно.
При компиляции, сначала происходит поиск метода или переменной, а уже после— проверка уровня доступа к ним
365
classDevice{ |
Если метод turn_on() не был переопределен в |
|
private: |
||
Laptop, вызов Laptop_instance.turn_on(), |
||
void turn_on() { cout<< “Deviceis on."<< endl; } |
||
приведетк ошибке при компиляции. |
||
}; |
|
|
classComputer: publicDevice {}; |
Объект Laptopможет получить доступ к двум |
|
classMonitor: publicDevice{}; |
определениямметода turn_on() |
|
одновременно: |
||
classLaptop: publicComputer,publicMonitor |
||
Device:Computer:Laptop.turn_on() и |
||
{ |
Device:Monitor:Laptop.turn_on() |
|
/* public: |
|
voidturn_on(){ cout<< "Laptopison."<< endl; } // решит проблему
*/
};
intmain(){
LaptopLaptop_instance;
//Laptop_instance.turn_on();// ошибка,если Laptop.turn_onзакомментирована
//callingmethodof specific superclass
Laptop_instance.Monitor::turn_on();
// treatingLaptopinstanceasMonitorinstancevia staticcast static_cast<Monitor&>(Laptop_instance).turn_on(); return0;
}
367
2/27/2023
Проблемы множественного наследования
Выход:
1.использованиевиртуальных базовыхклассовдля запрещения дублированияповторяющихсяструктур
2.вызватьметодконкретногосуперкласса
3.обратитьсяк объекту подклассакак к объекту определенного суперкласса
4.переопределить проблематичныйметодв последнем дочернем классе
366
Типы наследования
•публичный (public)- публичные (public) и защищенные (protected) данные наследуются без изменения уровня доступа к ним;
•защищенный (protected) — все унаследованные данные становятся защищенными;
•приватный (private) — все унаследованные данные становятся приватными.
368
92
class Device{ public:
int serial_number= 12345678; void turn_on() {
cout<< "Device is on" << endl; }
};
class Computer: private Device{ public: void say_hello(){ turn_on();
cout<< "Welcome to Windows11!" << endl;}
};
int main() { |
// Computer использует метод turn_on() как |
Device Device_instance; |
// любой приватный метод. turn_on() может |
Computer Computer_instance; |
// быть вызван изнутри класса, но попытка |
Device_instance.turn_on(); |
// вызвать его из main приведет к ошибке. |
Computer_instance.say_hello();// Для базового класса Device, turn_on() остался |
|
return 0; |
// публичным, и может быть вызван из main |
} |
369 |
Иерархия
371
2/27/2023
Иерархия
•Абстракции
образуют
иерархию
370
Конструкторы, деструкторы и наследование
•В C ++ конструкторы и деструкторы не наследуются.
•Однако они вызываются, когда дочерний класс инициализирует свой объект.
•Конструкторы вызываются один за другим
иерархически, начиная с базового класса и
заканчивая последним производным классом.
•Деструкторы вызываются в обратном порядке.
372
93
classDevice { public:
Device() { cout << "Deviceconstructor called" << endl; } // constructor ~Device() { cout << "Devicedestructor called" << endl; } // destructor
};
classComputer: public Device{ public:
Computer() { cout << "Computer constructor called" << endl; } ~Computer() { cout << "Computer destructor called" << endl; }
};
classLaptop:public Computer { public:
Laptop() { cout << "Laptopconstructor called" << endl; } ~Laptop() { cout << "Laptopdestructor called" << endl; }
};
int main(){
cout << "\tConstructors"<< endl; LaptopLaptop_instance;
cout << "\tDestructors"<< endl; return0;
} |
373 |
|
Конструкторы, деструкторы и проблема ромба
classDevice { public:
Device() { cout << "Deviceconstructor called" << endl; }
};
classComputer: public Device{ public:
Computer() { cout << "Computer constructor called" << endl; }
};
classMonitor: public Device{
public: Monitor() { cout << "Monitor constructor called" << endl; } };
classLaptop:public Computer, public Monitor {}; |
конструктор |
|
базового |
||
|
||
int main(){ |
класса Device будет |
|
LaptopLaptop_instance; return 0; |
вызван дважды !! |
|
} |
|
375
2/27/2023
classDevice { public:
Device() { cout << "Deviceconstructor called" << endl; } // constructor ~Device() { cout << "Devicedestructor called" << endl; } // destructor
};
classComputer: public Device{ public:
Computer() { cout << "Computer constructor called" << endl; } ~Computer() { cout << "Computer destructor called" << endl; }
};
classLaptop:public Computer { public:
Laptop() { cout << "Laptopconstructor called" << endl; } ~Laptop() { cout << "Laptopdestructor called" << endl; }
};
int main(){
cout << "\tConstructors"<< endl; LaptopLaptop_instance;
cout << "\tDestructors"<< endl; return0;
} |
374 |
|
Конструкторы, деструкторы и проблема ромба
classDevice{ public:
Device() { cout<< "Deviceconstructorcalled"<< endl; } void turn_on() { cout<< "Deviceis on."<< endl;}
};
classComputer: virtualpublicDevice{ public:
Computer(){ cout<< "Computerconstructorcalled"<< endl; }
};
classMonitor: virtualpublicDevice{
public:Monitor(){ cout<< "Monitorconstructorcalled"<< endl; } };
classLaptop: publicComputer,publicMonitor{};
intmain(){
LaptopLaptop_instance; Laptop_instance.turn_on(); return0;
}
Виртуальное наследование предотвращает появление множественных объектов базового класса в иерархии наследования.
Конструктор базового класса Device будет вызван только единожды, а обращение к методу turn_on() без его переопределения в дочернем классе не будет вызывать ошибку при компиляции.
376
94
Полиморфизм
•Полиморфизм– это свойство системы использовать объекты с одинаковыминтерфейсом без информациио типе и внутренней структуреобъекта.
•Полиморфизмомв ООП называется переопределение наследником функций-членовбазового класса
377
class Animal
{
...
virtual void Speak() const;
};
class Cat : public Animal
{
...
void Speak() {cout“Meow!”};
};
Animal *a[10];
a[0] = newCat(“Кыскерс”, 3); a[1] = newDog(“Оскар”, 5);
...
for (inti = 0; i< 10; i++) a[i]->Speak();
class Dog: public Animal
{
...
void Speak() {cout“Woof!”;};
};
•Для каждого элемента массива будет вызвана Cat::Speak() или Dog::Speak() в зависимости от животного
Такой полиморфизм называется динамическим
379
2/27/2023
class Animal |
class Dog: public Animal |
{ |
{ |
... |
... |
void Speak(){cout“No”;}; |
void Speak() {cout“Woof!”;}; |
}; |
}; |
class Cat : public Animal |
|
{ |
|
... |
|
void Speak() {cout“Meow!”}; |
|
}; |
|
•Какая именно из функций будет вызвана — Animal::Speak(), Cat::Speak() или Dog::Speak() — определяется во время компиляции.
Cat c = newCat(“Кыскерс”, 3); |
// Конструктор класса |
Animal *a = c; |
// Всё ok: Animal — базовый класс для Cat |
c->Speak(); |
// будет вызвана Cat::Speak() |
a->Speak(); |
// Animal::Speak(), поскольку a - |
|
// указатель на объект класса Animal |
Такой полиморфизм называется статическим 378
Чистая виртуальная функция
•Чистойвиртуальнойфункциейназываетсявиртуальная функция-член,котораяобъявленасо спецификатором= 0 вместо тела:
class Animal
{
...
virtual voidSpeak() const = 0;
);
•Чистая виртуальная функция не имеет определения и не может быть непосредственно вызвана.
•Она создает в общем базовом классе сигнатуру-прототип, которая не имеет собственного определения, но позволяет создавать такие определения в классах-потомках и вызывать их через указатель на общий базовый класс.
•Функция-член объявляется чистой виртуальной тогда, когда её определение для базового класса не имеет смысла.
380
95
Абстрактный класс
classDevice{ public:
void turn_on() { cout<< "Deviceis on."<< endl;}
virtual voidsay_hello()= 0; //есть чистый виртуальныйметод - абстрактный
};
/*
classDevice{ public:
virtual voidsay_hello()= 0; //есть только чистый виртуальныйметод -интерфейс
};
*/
classLaptop: publicDevice { public:
void say_hello(){ cout<< "Hello world!"<< endl;}
};
intmain(){
LaptopLaptop_instance; Laptop_instance.turn_on(); Laptop_instance.say_hello();
// Device Device_instance;// вызоветошибку компиляции return0;
}
381
383
2/27/2023
Механизм работы virtual
•Если в базовом классе объявлена хотя б одна виртуальная функция, то для каждого полиморфного класса (базового и всех производных) создается таблица виртуальных функций –
одномерный массив указателей на функции.
•Количество элементов в массиверавно количеству виртуальных функций в классе.
•Для всех полиморфных классов таблица будет содержать разные значения. Для каждого класса здесь будут записаны адреса методов данного класса.
•Помимо создания таблицы виртуальных функций, в базовом классе объявляется поле __vfptr – указатель на vtable. Этот указатель наследуется всеми производными классами. __vfptr указывает на vtable класса,которому принадлежит объект.
•Если процессор видит, что метод виртуальный, то ищет в таблице нужную запись. Адрес таблицы он узнает через __vfptr.
382
Абстрактный класс
•Абстрактным классом называетсятакой, у
которого есть хотя бы одна не переопределённая чистая виртуальная функция-член.
•Экземпляры таких классов создавать запрещено, абстрактные классы могут использоваться только для порождения новых классов путём наследования.
•Если в классе-потомке абстрактного класса не переопределены все унаследованныечистые виртуальные функции, то такой класс также является абстрактным и на него распространяются все указанныеограничения.
384
96
Виртуальный деструктор
•Основноеправило: если у вас в классе присутствует хотябы одна виртуальная функция, деструктор также следует сделать виртуальным.
•При этом не следует забывать, что деструктор по умолчанию виртуальным не будет, поэтому следует объявить его явно.
•Если этого не сделать, у вас в программе почти наверняка будут утечки памяти (memory leaks).
385
class A { public:
A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; }
};
class B : public A { public:
B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; }
};
int main()
{
A * pA = new B; delete pA;
return EXIT_SUCCESS;
}
387
2/27/2023
class A { |
class A { |
|
public: |
public: |
|
A() { cout << "A()" << endl; } |
A() { cout << "A()" << endl; } |
|
~A() { cout << "~A()" << endl; } |
virtual ~A() { cout << "~A()" << |
|
}; |
endl; } |
|
|
}; |
|
class B : public A { |
|
|
public: |
class B : public A { |
|
B() { cout << "B()" << endl; } |
public: |
|
~B() { cout << "~B()" << endl; } |
B() { cout << "B()" << endl; } |
|
}; |
~B() { cout << "~B()" << endl; } |
|
|
}; |
|
int main() |
|
|
{ |
int main() |
|
A * pA = new B; |
{ |
|
delete pA; |
A * pA = new B; |
|
return EXIT_SUCCESS; |
delete pA; |
|
} |
return EXIT_SUCCESS; |
|
|
} |
|
A() , B() , ~A() |
A(), B(), ~B(), ~A() |
|
|
|
|
Объект конструируется так,как и надо, а при |
Для вызова деструктора используется позднее |
|
разрушении происходитутечка памяти,потомукак |
связывание,то естьпри разрушении объектаберется |
|
деструктор производногоклассане вызывается. |
указатель на класс, затемиз таблицы виртуальных |
|
Удаление производитсячерезуказатель на базовый |
функций определяетсяадрес нужного нам |
|
класси для вызовадеструкторакомпилятор |
деструктора,а это деструктор производногокласса, |
|
использует раннее связывание.Деструктор базового |
которыйпосле своейработы,как и полагается, |
|
класса не может вызватьдеструктор производного, |
вызываетдеструктор базового.Итог: объектразрушен386 |
, |
потомучто он о нем ничегоне знает. |
памятьосвобождена. |
|
class A { public:
A() { cout << "A()" << endl; } virtual ~A() { cout << "~A()" << endl;
}
};
class B : public A { public:
B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; }
};
int main()
{
A * pA = new B; delete pA;
return EXIT_SUCCESS;
}
388
97
Модульность
•Модульность – свойство системы, котораябыла разложена на внутренне связанные, но слабо связанные между собой модули.
•Структура модуля должна быть простой для восприятия.
•Реализация модуля не должна зависеть от других модулей.
•Должны быть приняты меры для облегчения процесса внесения изменений.
•Баланс между сокрытиеминформациии необходимостью обеспечения видимости тех или иных абстракций в несколькихмодулях.
•Интерфейсы модулей необходимо документировать.
389
Типизация
•Типизация – это способ защититься от использования объектов одного класса вместо другого, или по крайней мере управлять таким использованием.
391
2/27/2023
390
Сохраняемость
•Сохраняемость – способность объекта существовать во времени, переживая породивший его процесс, и (или) в пространстве, перемещаясь из своего начальногоадресного пространства.
•Сериализация – процесс перевода какойлибо структуры данныхв последовательностьбитов.
•Обратнойк операции сериализацииявляется операция десериализации (структуризации) - восстановлениеначального состояния структуры данных из битовой последовательности.
392
98
Сохраняемость
Параллелизм
•Параллелизм– свойство,отличающее активныеобъектыот пассивных.
•Разделениепредметнойобластина объектыпозволит разнестиобщийфункционалсистемыпо нескольким
потоками процессам, частьиз которых сможет выполняться одновременно.
395
2/27/2023
Сохраняемость
•Любой объект существует в памяти и живет во времени. Объекты:
1.Промежуточныерезультаты вычислений выражений
(ЯП).
2.Локальные переменные и объекты в блоках,а также при вызове процедур и функций(ЯП).
3.Статические переменные классов, а также глобальные переменныеи объекты в динамической памяти (ЯП).
4.Данные, сохраняемые между сеансами выполнения программы(БД).
5.Данные, сохраняемые при переходена другую версию программы(БД).
6.Данные переживающиепрограммупо времени (БД).394
Принципы OOD SOLID
•Single responsibility (Принцип единственной ответственности)
•Open-closed (Принцип открытости/закрытости)
•Liskov substitution (Принцип подстановки Барбары Лисков)
•Interface segregation (Принцип разделения интерфейса)
•Dependency inversion (Принцип инверсии зависимостей)
396
99
Принцип единственной ответственности
•Принцип единственной обязанности (Single responsibility) обозначает, что каждый объект должен иметь одну обязанность, и эта обязанность должна быть полностью инкапсулирована в класс.
•Все возможности объекта должны быть направлены исключительно на обеспечение этой обязанности.
397
Принцип единственной ответственности
399
2/27/2023
Принцип открытости/закрытости
•Принцип открытости/закрытости(Open-closed): «программныесущности (классы, модули, функциии т. п.) должны быть открыты для расширения,но закрыты для изменения».
•Такие сущности могутпозволять менять свое поведение без изменения их исходного кода.
•“Однаждыразработаннаяреализациякласса в дальнейшемтребует только исправленияошибок, а новые или изменённыефункции требуют создания нового класса”// БертранМейер.
400
100