Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Яп

.pdf
Скачиваний:
28
Добавлен:
15.03.2023
Размер:
6.44 Mб
Скачать

2/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