68971

Віртуальні функції. Абстрактні класи

Лекция

Информатика, кибернетика и программирование

Кожне перевизначення віртуальної функції в похідному класі реалізує операції властиві лише даному класу. Покажчики на об’єкти базового класу можна використовувати для посилання на об’єкти похідних класів. Якщо покажчик на об’єкт базового класу встановлюється на об’єкт...

Украинкский

2014-09-28

54 KB

2 чел.

Лекція № 16

Тема: Віртуальні функції. Абстрактні класи.

План

  1.  Віртуальні функції
  2.  Виклик віртуальної функції з допомогою посилання на об’єкт базового класу
  3.  Атрибут virtual успадковується
  4.  Віртуальні функції є ієрархічними
  5.  Чисто віртуальні функції
  6.  Абстрактні класи

Мова C++ забезпечує як статичний, так і динамічний поліморфізм. Як указувалося в попередніх розділах, статичний поліморфізм досягається за допомогою перевантаження функцій і операторів. Динамічний поліморфізм реалізується на основі спадкоємства і віртуальних функцій. Саме ця тема знаходиться в центрі уваги даного розділу.

Віртуальні функції

Віртуальна функція (virtual function) — це функція-член, оголошена в базовому класі і перевизначена в похідному. Щоб створити віртуальну функцію, слід вказати ключове слово virtual перед її оголошенням в базовому класі. Похідний клас перевизначає цю функцію, пристосовувавши її для своїх потреб. По суті, віртуальна функція реалізує принцип "один інтерфейс, декілька методів", лежачий в основі поліморфізму. Віртуальна функція в базовому класі визначає вид інтерфейсу, тобто спосіб виклику цієї функції. Кожне перевизначення віртуальної функції в похідному класі реалізує операції, властиві лише даному класу. Інакше кажучи, перевизначення віртуальної функції створює конкретний метод (specific method).

При звичайному виклику віртуальні функції нічим не відрізняються від решти функцій-членів. Особливі властивості віртуальних функцій виявляються при їх виклику за допомогою покажчиків. Покажчики на об'єкти базового класу можна використовувати для посилання на об'єкти похідних класів. Якщо покажчик на об'єкт базового класу встановлюється на об'єкт похідного класу, що містить віртуальну функцію, вибір необхідної функції ґрунтується на типі об'єкту, на який посилається покажчик, причому цей вибір здійснюється в ході виконання програми. Таким чином, якщо покажчик посилається на об'єкт різних типів, то будуть викликані різні віртуальні функції. Це відносите і до посилань на об'єкти базового класу.

Розглянемо спершу наступний приклад.

#include <iostream>

using namespace std;

class base {

public:

virtual void vfunc() {

cout << "Функція vfunc() з класу base.\n";

}

};

class derived1 : public base {

public:

void vfunc() {

cout « "Функція vfunc() з класу derived1.\n";

}

};

class derived2 : public base {

public:

void vfunc() {

cout « " Функція vfuncO з класу derived2.\n" ;

}

};

int main() (

base *p, b;

derived1 d1;

derived2 d2 ;

// Покажчик на об'єкт базового класу.

Р = &Ь;

p->vfunc(); // Виклик функції vfunc() з класу base.

// Покажчик на об'єкт класу derived1.

р = &d1;

p->vfunc(); // Виклик функції vfunc(j з класу derived1.

// Покажчик на об'єкт класу derived2.

р = &d2;

p->vfunc(); // Виклик функції vfunc() з класу derived2.

return 0;

}

Ця програма виводить на екран наступні рядки.

Функція vf unc () з класу base.

Функція vfunc() з класу derived1.

Функція vfunc () з класу derived.2 .

Як показує ця програма, усередині класу base оголошена віртуальна функція vfunc (). Зверніть увагу на ключове слово virtual в оголошенні функції. При перевизначенні функції vfunc () у класах derived1 і derived2 ключове слово virtual не потрібне. (Проте його використання не є помилкою, просто воно не обов'язкове.)

У даній програмі класи derived1 і derived2 є похідними від класу base. Усередині кожного з цих класів функція vfunc () перевизначається наново відповідно до нового призначення. У програмі main() оголошені чотири змінні.

p - Покажчик на базовий клас

b - Об'єкт базового класу

d1 - Об'єкт класу derived1

d2 - Об'єкт класу derived2

Крім того, покажчику р привласнюється адреса об'єкту b, а функція vfunc() викликається за допомогою покажчика р. Оскільки покажчик р посилається на об'єкт класу base, виконується варіант функції vfunc () з базового класу. Потім покажчику р привласнюється адреса об'єкту d1, і функція vfunc () знову викликається з його допомогою. Цього разу покажчик р посилається на об'єкт класу derived1. Отже, викликається функція derived1:: vfunc (). В результаті покажчику р привласнюється адреса об'єкту d2, тому вираз p->vfunc() приводить до виклику функції vfunc Про з класу de-rived2. Принципово важливо, що варіант функції, що викликається, визначається типом об'єкту, на який посилається покажчик р. Крім того, вибір відбувається в ході виконання програми, що забезпечує основу динамічного поліморфізму.

Віртуальну функцію можна викликати звичайним способом, використовуючи ім'я об'єкту і оператора ".", проте поліморфізм досягається тільки при зверненні до неї через покажчик. Наприклад, наступний фрагмент програми є абсолютно правильним.

d2.vfunc(); // Викликається функція vfunc() з класу derived2

Не дивлячись на те що такий виклик віртуальної функції помилкою не є, ніяких переваг він не надає.

На перший погляд, перевизначення віртуальної функції в похідному класі мало відрізняється від звичайного перевантаження функцій. Проте це не так, і термін перевантаження непридатний до перевизначення віртуальних функцій з кількох причин. Найбільш важлива відмінність полягає в тому, що прототип віртуальної функції, що перевизначається, повинен точно співпадати з прототипом, визначеним в базовому класі. віртуальні функції відрізняються від переобтяжених, які відрізняються типами і кількістю параметрів. (Фактично при перевантаженні функцій типи і кількість i параметрів повинні відрізнятися! Саме ці відмінності дозволяють компілятору вибрати правильний варіант переобтяженої функції.) При перевизначенні віртуальної функції всі аспекти їх прототипів повинні бути однаковими. Якщо не дотримував правило, компілятор вважатиме ці функції просто переобтяженими, а їх віртуальна природа буде втрачена. Друге важливе обмеження полягає в тому, що віртуальні функції не можуть бути статичними членами класів. Крім того, ой можуть бути дружніми функціями. І, нарешті, конструктори не можуть бути віртуальними, хоча на деструкції це обмеження не розповсюджується.

Із-за перерахованих обмежень для перевизначення віртуальної функції в похідному класі використовується термін заміщення (overriding).

Виклик віртуальної функції з допомогою

посилання на об'єкт базового класу

У попередньому прикладі віртуальна функція викликалася за допомогою покажчика об'єкт базового класу, проте поліморфна природа віртуальних функцій збережи і при їх виклику за допомогою посилання на об'єкт базового класу. Посилання є неявним покажчиком. Таким чином, посилання на об'єкт базового Л можна використовувати для звернення до об'єкту базового або будь-якого похідного класу. Якщо віртуальна функція викликається за допомогою посилання на об'єкт базового класу, її варіант залежить від типу об'єкту, на який встановлено посилання у момент виклику.

В більшості випадків віртуальна функція за допомогою посилання викликається передачі параметрів. Розглянемо ще один варіант попередньої програми

/* У цій програмі для виклику віртуальної функції застосовується посилання на об'єкт базового класу. */

// Використовується параметр, що є посиланням на об'єкт базового класу.

void f (base &r)

{

r.vfuncO ;

}

int main() {

base b;

derived1 d1;

derived2 d2 ;

f(b); // Функції f() передається об'єкт класу base.

f(d1); // Функції f() передається об'єкт класу derived1.

f(d2); // Функції f() передається об'єкт класу derived2.

return 0;

}

Програма виводить на екран ті ж повідомлення, що і раніше. У даному прикладі функція f() отримує як параметр посилання на об'єкт класу base. У функції main() ця функція викликається за допомогою об'єктів класів base, derived1 і derived2. Конкретний варіант функції vfunc() вибирається усередині функції f() залежно від типу її параметра, простоти в решті прикладів цього розділу віртуальні функції викликаються з покажчиків, причому результат нічим не відрізняється від виклику за допомогою посилань.

Атрибут virtual успадковується

При наслідуванні віртуальної функції її віртуальна природа також успадковується. Це означає, що якщо похідний клас, що успадкував віртуальну функцію від базового класу, стає базовим по відношенню до іншого похідного класу, віртуальна функція може як і раніше заміщатися. Інакше кажучи, не має значення, скільки разів успадковувалася віртуальна функція, вона все одно залишається віртуальною. Розглянемо наступну програму.

#include <iostream>

using namespace std;

class base {

public:

virtual void vfunc () {

cout << "Функція vfunc() з класу base.\n";

)

class derived1 : public base {

public: roid vfunc () {

cout << " Функція vfunc() з класу derived1.\n";

}

};

/* Клас derived2 успадковує віртуальну функцію vfunc()

від класу derived1. */

class derived2 : public derived1 {

public:

// Функція vfunc() залишається віртуальною,

void vfunc() {cout « " Функція vfunc() з класу derived2.\n"; }

};

Виклик функції vfunc() з класу base.

Виклик функції vfunc() з класу derived1.

Виклик функції vfunc() з класу derived2.

В даному випадку клас derived2 є спадкоємцем класу derived1, а класу base, але функція vfunc() залишається віртуальною.

Віртуальні функції є ієрархічними

Як відомо, якщо функція оголошена віртуальною в базовому класі, її можна замістити в похідному класі. Проте віртуальну функцію не обов'язково заміщати. В цьому випадку викликається функція, визначена в базовому класі.. Спадкоємство в мові C++ організоване за ієрархічним принципом, тому віртуальні функції також повинні бути ієрархічними. Це означає, що якщо віртуальна функція не заміщається, викликається її попередня перевизначена версія. Наприклад, в наступній програмі клас derived2 є спадкоємцем класу derived1, який, у свою чергу, є похідним від класу base. Все функція vfunc () у класі derived2 не заміщається. Отже, найближча до класу derived2 версія функції vfunc Про визначена в класі derived1. Taким чином, виклик функції vfunc () за допомогою об'єкту класу derived2 відносить до функції derived1:: vfunc ().

Чисто віртуальні функції

Якщо віртуальна функція не заміщається в похідному класі, викликається її версія з базового класу. Проте у багатьох випадках неможливо створити розумну версію віртуальної функції в базовому класі. Крім того, в деяких ситуаціях необхідно гарантувати, що віртуальна функція буде заміщена у всіх похідних класах. Для цих ситуацій в мові С++ передбачені чисто віртуальні функції.

Чисто віртуальна функція (pure virtual function) – це функція, що не має визначення в базовому класі. Для оголошення чисто віртуальної функції використовується наступна синтаксична конструкція.

virtual тип імя_функції (список_параметрів) = 0;

Чисто віртуальні функції повинні перевизначатися в кожному похідному класі, інакше виникне помилка компіляції.

Слід мати на увазі, що всі похідні класи зобов'язані перевизначати чисто віртуальну функцію. Якщо цього не зробити, виникне помилка компіляції.

Абстрактні класи

Клас, що містить хоч би одну чисто віртуальну функцію, називається абстрактним (abstract class). Оскільки абстрактний клас містить одну або декілька функцій, що не мають визначення (т.е. чисто віртуальні функції), його об'єкт створити неможливо. Отже, абстрактні класи можна чи використовувати як основу для похідних класів.

Не дивлячись на те що об'єкти абстрактного класу не існують, можна створювати покажчики і посилання на абстрактний клас. Це дозволяє застосовувати абстракту класи для підтримки динамічного поліморфізму і вибирати відповідаю віртуальну функцію залежно від типу покажчика або посилання.


 

А также другие работы, которые могут Вас заинтересовать

21163. Чипы памяти. Проектирование модулей памяти 43.5 KB
  Поскольку частота синхронизации внешних и внутренних цепей любого совершенствующегося DRAMинтерфейса постоянно увеличивается особое внимание должно уделяться целостности цифрового сигнала: его логическим уровням фоновым шумам шумам коммутации терминированию топологии сигнальных трасс рассеиваемой мощности терморегуляции и уменьшению влияния ЭМИ. Электромагнитная интерференция Высокие частоты критические условия большие значения силы тока прохождение и ветвление сигнальных трасс все это способствует возникновению самого опасного...
21164. ЭКОЛОГИЯ ПРОИЗВОДСТВА СВТ 299.5 KB
  Вредные вещества в помещении находятся в виде пыли тонкодисперсного тумана паров и газов. Обработка на станках сопровождается выделением пыли стружки туманов масел и эмульсий которые через вентиляционную систему выбрасываются из помещений. Количество выделяющейся пыли зависит от размеров и твердости обрабатываемого материала. При обработке текстолита выделение пыли составляет от 20 г ч до 120 г ч на единицу оборудования; стеклоткани от 9 г ч до 20 г ч; органического стекла от 800 г ч до 950 г ч.
21166. Общие понятия эксплуатации. Техническое обслуживание СВТ 49.5 KB
  Техническое обслуживание СВТ Эксплуатация ЭВМ заключается в использовании машины для выполнения всего комплексах возложенных на нее задач. Для эффективного использования и поддержания ЭВМ в работоспособном состоянии в процессе эксплуатации производится техническое обслуживание ТО. ТО это комплекс организационных мероприятий предназначенных для эксплуатации и ремонта ЭВМ. Существует 3 вида ТО: индивидуальный; групповой; централизованный; При индивидуальном ТО обеспечивается обслуживание одной машины силами и средствами персонала...
21167. ЭЛЕКТРИЧЕСКИЕ СОЕДИНЕНИЯ В КОНСТРУКЦИЯХ СВТ 609 KB
  В зависимости от конструктивных особенностей обратного провода ЛП подразделяют на симметричные состоящие из двух одинаковых изолированных проводов несимметричные с одним общим проводом для многих ЛП и коаксиальные с обратным проводом по оплетке коаксиального кабеля. В поперечном сечении провода бывают круглыми или прямоугольными пленочные и печатные проводники прямоугольными. Провода защищаются изолирующими диэлектрическими оболочками а при необходимости экранами. Линии электропитания представляют собой объемные провода пленочные и...
21168. Микропроцессоры 1970-х – 1990-х годов: архитектура и эволюция 439.5 KB
  Новое поколение микропроцессоров ознаменовалось появлением 32битных процессоров 80386 1985 и 486SX 1989 которые могли адресовать до 4 Гбайт памяти и выполнять несколько задач одновременно. Каждая ячейка хранит часть или все данное или команду и с ней ассоциируется идентификатор называемый адресом памяти или просто адресом. Центральный процессор последовательно вводит или выбирает команды из памяти и выполняет определяемые ими задачи. К середине 1990х годов однако из магнитных устройств внешней памяти остались в использовании...
21169. ПРОМЫШЛЕННЫЕ РОБОТЫ 43.5 KB
  По требованию к точности манипулирования различают роботы нормальной точности с погрешностью позиционирования в зависимости от грузоподъемности 01 5 мм прецизионные роботы с погрешностью 5 мкм и ультрапрецизионные роботы с погрешностью до 003 мкм. Роботы нормальной точности применяют для манипулирования транспортными или технологическими кассетами перекладки полупроводниковых пластин из кассеты в кассету на химических операциях. Прецизионные роботы манипулируют пластинами или кристаллами на операциях посадки кристалла разводки...
21170. РАСЧЕТ ПОТРЕБЛЯЕМОЙ МОЩНОСТИ 185.5 KB
  1 РАСЧЕТ ПОТРЕБЛЯЕМОЙ МОЩНОСТИ Потребляемая мощность всей платы будет зависеть от потребляемой мощности отдельных элементов и количества микросхем.1 Потребляемая мощность микросхем Тип микросхемы Количество корпусов Мощность потребляемая одним корпусом мВт Мощность потребляемая всеми корпусами мВт MAX1106 1 445 445 AD232 1 696 696 где Pпотр потребляемая мощность всей платы P мощность одной микросхемы n количество микросхем. В итоге: Pпотр = 445 696 = 1141 мВт Таким образом потребляемая мощность платы составила всего около 1 Вт...
21171. Расчет надежности 22 KB
  Для выполнения приближенного расчета необходимо знать усредненные значения интенсивностей отказов λi типовых элементов и число Ni элементов определенного типа в каждой группе. В группе объединяются элементы которые имеют примерно одинаковую интенсивность отказов. Для полного расчета надежности необходимо иметь данные о реальных режимах работы элементов устройства и о зависимостях интенсивностей отказов элементов от температурных электрических и других режимов и нагрузок.