Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СОТ СП.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
11 Мб
Скачать
☆
  1. Практичні прийоми програмування з використанням об’єктів.

Поліморфізм. Одним із базових понять технології об'єктно-орієнтованого програмування є поліморфізм. Цей термін має грецьке походження і приблизно означає "багато форм" (poly - багато, morphos - форма).

Поліморфізм - це засіб для надання різних значень одній і тій же події в залежності від типу оброблюваних даних. Цей принцип визначає різні форми реалізації однойменної дії.

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

У рамках C++ Builder поведінкові властивості класу визначаються набором методів, що входять до нього. Цей принцип використовується, коли потрібно розширити властивості класу не шляхом додавання нових методів, а шляхом добудови одного з методів чи набору методів. Змінюючи алгоритм того чи іншого методу в нащадках класу, програміст може надавати цим нащадкам відсутні у батька специфічні властивості.

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

Крім цього, в C++ Builder поліморфізм досягається не лише механізмом спадковості й перекриття методів батька, але й їхньою віртуалізацією, що дозволяє батьківським методам звертатися до методів своїх нащадків.

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

Використання специфікатора доступу protected для успадкування базового класу

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

Код програми. Демонстрація механізму доступу до членів базового классу після їх успадковується як public-клас

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

int c, d;

public:

void setB(int a, int b) { c = a; d = b; }

void showB(char *s)

{ cout << s << "c= " << c << "; d= " << d << endl; }

};

class derived : public baseClass {

int f;

public:

derived(int x) { f = x; }

void showF(char *b, char *p)

{ showB(b); cout << p << "f= " << f << endl; }

};

int main()

{

derived ObjD(3);

// Доступ до членів класу baseClass

ObjD.setB(1, 2);

// Доступ до членів класу baseClass

ObjD.showB("Базовий клас: "); cout << endl;

// Доступ до члена класу derived

ObjD.showF("Базовий клас: ", "Похідний клас: ");

getch(); return 0;

}

Внаслідок виконання ця програма відображає на екрані такі результати:

Базовий клас: c= 1; d= 2

Базовий клас: c= 1; d= 2

Похідний клас: f= 3

Оскільки функції setB() і showB() (члени класу baseClass) успадковані класом derived як public-члени, то їх можна викликати для об'єкта типу derived у функції main(). Позаяк члени даних c та d визначені як private-члени, то вони залишаються закритими у межах свого класу baseClass.

Протилежністю відкритому (public) успадкуванню є закрите (private).

Наприклад, наведений нижче код програми не відкомпілюється, оскільки обидві функції setB() і showB() тепер стали private-членами класу derived (тобто, доступними тільки для функцій-членів похідного класу), і тому їх не можна викликати з функції main().

Код програми . Демонстрація механізму доступу до членів базового класу після їх успадковується як private-клас

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

Об'єктно-орієнтоване програмування мовою С++

122 Розділ 5. Організація механізмів успадкування в класах

class baseClass { // Оголошення базового класу

int c, d;

public:

void setB(int a, int b) { c = a; d = b; }

void showB(char *s) { cout << s << "c= " << c << "; d= " << d << endl; }

};

// Відкриті члени класу baseClass тепер стають закритими членами класу derived.

class derived : private baseClass {

int f;

public:

derived(int x) { f = x; }

void showF(char *b, char *p) { showB(b); cout << p << "f= " << f << endl; }

};

int main()

{

derived ObjD(3);

// Помилка, доступу до функції setB() немає.

ObjD.setB(1, 2);

// Помилка, доступу до функції showB() немає.

ObjD.showB("Базовий клас: "); cout << endl;

// Доступ до члена класу derived

ObjD.showF("Базовий клас: ", "Похідний клас: ");

getch(); return 0;

}

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

Код програми . Демонстрація механізму доступу до захищених членів базового класу

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

protected:

int c, d; // Ці члени закриті у класі baseClass але доступні для класу derived.

public:

void setB(int a, int b) { c = a; d = b; }

void showB(char *s) { cout << s << "c= " << c << "; d= " << d << endl; }

};

// Оголошення похідного класу

class derived : public baseClass {

int f;

public:

// Клас derived має доступ до членів класу baseClass c та d.

void setF() { f = c*d; }

void showF(char *b, char *p) { showB(b); cout << p << "f= " << f << endl; }

};

int main()

{

derived ObjD; // Створення об'єкта класу

// OK, класу derived це робити дозволено.

ObjD.setB(2, 3);

// OK, класу derived це робити дозволено.

ObjD.showB("Базовий клас: "); cout << endl;

// Функція setF() належить класу derived.

ObjD.setF();

// Функція showF() належить класу derived.

ObjD.showF("Базовий клас: ", "Похідний клас: ");

getch(); return 0;

}

Наприклад, у наступній (цілком коректній) програмі клас derivedB має повноправний доступ до членів-даних c та d початкового базового класу.

Код програми . Демонстрація механізму доступу до захищених членів базового класу похідними класами

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

protected:

int c, d;

public:

void setB(int a, int b) { c = a; d = b; }

void showB(char *s) { cout << s << "c= " << c << "; d= " << d << endl; }

};

// Члени c та d успадковуються як protected-члени.

class derivedA : public baseClass {

int f;

public:

void setF() { f = c*d; } // Правомірний доступ

void showF(char *b, char *p) { showB(b); cout << p << "f= " << f << endl; }

};

// Члени c та d успадковуються опосередковано через клас derivedA.

class derivedB : public derivedA {

int h;

public:

void setM() { h = c + d; } // Правомірний доступ

void showM(char *b, char *p1, char *p2)

{ showF(b, p1); cout << p2 << "h= " << h << endl; }

};

int main()

{

derivedA ObjA; // Створення об'єкта класу

derivedB ObjB; // Створення об'єкта класу

ObjA.setB(2, 3);

ObjA.showB("Базовий клас: "); cout << endl;

ObjA.setF();

ObjA.showF("Базовий клас: ", "Похідний клас: "); cout << endl;

ObjB.setB(3, 4);

ObjB.showB("Базовий клас: "); cout << endl;

ObjB.setF();

ObjB.setM();

ObjB.showF("Базовий клас: ", "Похідний клас 1: "); cout << endl;

ObjB.showM("Базовий клас: ", "Похідний клас 1: " , "Похідний клас 2: ");

getch(); return 0;

}

Внаслідок виконання ця програма відображає на екрані такі результати:

Базовий клас: c= 2; d= 3

Базовий клас: c= 2; d= 3

Похідний клас: f= 6

Базовий клас: c= 3; d= 4

Базовий клас: c= 3; d= 4

Похідний клас 1: f= 12

Базовий клас: c= 3; d= 4

Похідний клас 1: f= 12

Похідний клас 2: h= 7

Якщо базовий клас успадковується як закритий (тобто з використанням специфікатора доступу private), то захищені (derived) члени базового класу стають закритими (private) членами похідного класу. Отже, якби у наведеному вище прикладі клас baseClass успадковувався закритим способом, то всі його захищені члени стали б private-членами класу derivedA, і у цьому випадку вони не були б доступні для класу derivedB1. Цю ситуацію продемонстровано у наведеному нижче коді програми, яка через це є некоректною і не відкомпілюється. Всі помилки відзначені у коментарях.

Код програми . Демонстрація некоректного використання закритих членів

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

protected:

int c, d;

public:

void setB(int a, int b) { c = a; d = b; }

void showB(char *s) { cout << s << "c= " << c << "; d= " << d << endl; }

};

// Елементи класу baseClass будуть закриті у межах класу derivedA.

class derivedA : private baseClass {

int f;

public:

// Виклики цих функцій цілком законні, оскільки змінні c та d

// є одночасно private-членами класу derivedA.

void setF() { f = c*d; } // OK

void showF(char *b, char *p)

{ showB(b); cout << p << "f= " << f << endl; }

};

// Доступ до членів c, d, setB() і showB() не успадковується.

class derivedB : public derivedA {

int h;

public:

// Неправильно, оскільки члени-даних c та d закриті у межах класу derivedA.

void setM() { h = c + d; } // Помилка

void showM(char *b, char *p1, char *p2)

{ showF(b, p1); cout << p2 << "h= " << h << endl; }

};

int main()

{

derivedA ObjA; // Створення об'єкта класу

derivedB ObjB; // Створення об'єкта класу

ObjA.setB(1, 2);

// Помилка: не можна викликати функцію showB()

ObjA.showB("Базовий клас: "); cout << endl;

// Помилка: не можна викликати функцію setB()

ObjB.setB(3, 4);

// Помилка: не можна викликати функцію showB()

ObjB.showB("Базовий клас: ");

getch(); return 0;

}

Незважаючи на те, що клас baseClass успадковується класом derivedA закритим способом, однак клас derivedA має доступ до public- і protected-членів класу baseClass. Однак цей привілей він не може передати далі, тобто вниз за ієрархією класів.

Ключове слово protected – це частина мови C++. Воно забезпечує захист певних елементів класу від модифікації функціями, які не є членами цього класу, але дає змогу передавати їх "за успадкуванням".

Специфікатор доступу protected можна також використовувати стосовно структур. Але його не можна застосовувати до об'єднань, оскільки об'єднання не успадковується іншим класом. Деякі компілятори допускають використання специфікатора доступу protected в оголошенні об'єднання, але, оскільки об'єднання не можуть брати участь в успадкуванні, то у цьому контексті ключове слово protected буде рівносильним ключовому слову private.

Специфікатор захищеного доступу може знаходитися в будь-якому місці оголошення класу, але, як правило, protected-члени оголошуються після (оголошуваних за замовчуванням) private-членів і перед public-членами. Таким чином, найзагальніший формат оголошення класу зазвичай має такий вигляд:

class ім'я_класу {

private-члени

protected:

protected-члени

public:

public-члени

};

Нагадаємо, що розділ захищених членів є необов'язковим._

Код програми . Демонстрація механізму використання специфікатора доступу protected для успадкування базового класу

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

int c; // Закритий

protected:

int d; // Захищений

public:

int f; // Відкритий

void Set(int a) { c = a; }

int Put() { return c; }

};

// Успадковуємо клас baseClass як protected-клас.

class derived : protected baseClass {

public:

void setJ(int a) { d = a; } // d – тут protected-член

void setF(int a) { f = a; } // f – тут protected-член

int getJ() { return d; }

int getK() { return f; }

};

int main()

{

derived ObjD; // Створення об'єкта класу

/* Наступний рядок неправомірний, оскільки функція Set() є

protected-членом класу derived, що робить його недоступним за його межами. */

ObjD.Set(10);

cout << ObjD.Put(); // Неправильно, оскільки функція Put() -- protected-член.

ObjD.f = 10; // Неправильно, оскільки змінна f - protected-член

// Наступні настанови правомірні.

ObjD.setF(10);

cout << ObjD.getK() << " ";

ObjD.setJ(12);

cout << ObjD.getJ() << " ";

getch(); return 0;

}

Як зазначено у коментарях до цієї програми, члени (класу baseClass) f, d, Set() і Put() стають protected-членами класу derived. Це означає, що до них не можна отримати доступу з коду програми, "прописаного" поза класом derived. Тому посилання на ці члени у функції main() (через об'єкт ObjD) неправомірні.

Узагальнення інформації про використання специфікаторів доступу public, protected і private

Оскільки права доступу, що визначаються специфікаторами доступу public, protected і private, є принциповими для створення програм мовою C++, то є сенс узагальнити все те, що було сказано про ці ключові слова.

Оголошення членів класу:

● у випадку оголошення члена класу відкритим (з використанням ключового слова public), то до нього можна отримати доступ з будь-якої іншої частини програми;

● якщо член класу оголошується закритим (за допомогою специфікатора доступу private), то до нього можуть отримувати доступ тільки члени того ж самого класу. Понад це, до закритих членів базового класу не мають доступу навіть похідні класи;

● якщо член класу оголошується захищеним (тобто protected-членом), то до нього можуть отримувати доступ тільки члени того ж самого класу або похідних від нього.

Специфікатор доступу protected дає змогу успадковувати члени, але залишає їх закритими у межах ієрархії класів.

Успадкування базових класів:

● якщо базовий клас успадковується з використанням ключового слова public, то його public-члени стають public-членами похідного класу, а його protected-члени – protected-членами похідного класу;

● якщо базовий клас успадковується з використанням ключового слова private, то його public- і protected-члени стають private-членами похідного класу;

● якщо базовий клас успадковується з використанням специфікатора доступу protected, то його public- і protected-члени стають protected-членами похідного класу;

● в усіх випадках private-члени базового класу залишаються закритими у межах цього класу і не успадковуються.

У міру збільшення досвіду створення програм мовою C++ застосування специфікаторів доступу public, protected і private не завдаватиме Вам клопоту.