Деструктори

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

Деструктор автоматично викликається кожного разу при знищенні об'єкту. Він може бути використаний для звільнення пам'яті, зарезервованої для зберігання об'єкту класу, або для інших завдань, пов'язаних із завершенням життя об'єкту.

Ім'я деструктора співпадає з ім'ям класу і містить додатковий символ префікса "~".

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

Наприклад, якщо клас називається CMessage, його деструктор визначається таким чином:

 

~CMessage ()

{

// код деструктора

}

 

Деструктор може виконувати будь-які завдання, необхідні для видалення об'єкту. Наприклад, конструктор класу CMessage, приведеного нижче, виділяє блок пам'яті для зберігання рядка повідомлення, а деструктор звільняє пам'ять безпосередньо перед знищенням об'єкту класу:

 

#include <string.h>

class CMessage

{

private:

char *Buffer; // зберігає рядок повідомлення

public:

CMessage () // конструктор класу

{

Buffer = new char (''); // виділення пам'яті та ініціалізація

// буфера порожнім рядком

}

~CMessage() // деструктор класу

{

delete[] Buffer; // звільнення пам'яті

}

void Display ()

{

// код для відображення на екрані вмісту змінної Buffer

}

void Set (char *String) // установка нового рядка повідомлення

{

delete[] Buffer;

Buffer = new char [strlen (String) + 1];

strcpy (Buffer, String);

}

};

 

Деструктори не можна перевантажувати через відсутність у них аргументів.

7.1.6 Виклики конструкторів і деструкторів

Якщо говорити загалом, конструктор викликається при створенні об'єкту, а деструктор - при його знищенні.

У наступному списку детально перелічені моменти виклику конструкторів і деструкторів для різного виду об'єктів.

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

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

- Об'єкт оголошений локально з використанням специфікатора static. Конструктор викликається, коли потік управління вперше досягає оголошення об'єкту, деструктор — в кінці програми;

- Об'єкт створений динамічно з використанням оператора new. Конструктор викликається при створенні об'єкту, а деструктор - коли об'єкт явно знищується з використанням оператора delete. Якщо оператор delete для об'єкту не виконується, деструктор ніколи не буде викликаний.

7.1.7 Константні об'єкти і функції-члени класу

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

 

class CTest

{

public:

int A;

int B;

CTest (int AVal, int BVal)

{

A = AVal;

B = BVal;

}

};

 

Наступна інструкція створює об'єкт цього класу та ініціалізує обидві змінні-члени:

 

const CTest Test (1, 2);

Це перша й остання можливість присвоїти значення змінним об'єкту Test. Із-за специфікатора const неприпустимо присвоювання вигляду:

 

Test.A =3; // помилка - не можна змінювати змінні-члени

// об'єкту типу const

 

Існують інші варіанти оголошення об'єкту з використанням специфікатора const. Щоб продемонструвати їх, розглянемо наступне оголошення об'єкту класу CFrame:

 

const CFrame Frm(5, 5, 25, 25);

Хоча функція-член GetCoord() класу CFrame не змінює ніяких змінних-членів, компілятор не дозволить програмі викликати її для об'єкту з атрибутом const:

 

int L, Т, R, В;

Frm.GetCoord (&L &T, &R, &B); // помилка

 

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

Щоб мати можливість виклику функції GetCoord() для об'єкту типу const, потрібно включити специфікатор const в оголошення функції:

 

class CFrame

{

//...

void GetCoord (int *L, int *T, int *R, int *B) const

{

*L = Left;

*T = Top;

*R = Right;

*B = Bottom;

}

// …

};

 

Специфікатор const в оголошенні функції GetCoord() означає, що інструкції тіла функції-члена класу не змінюють змінні-члени цього класу. Якщо вона спробує це зробити, компілятор згенерує помилку при компіляції початкового коду.

Функцію GetCoord() зараз можна викликати для об'єкту типу const класу CFrame:

 

const CFrame Frm (5, 5, 25, 25);

int L, T, R, B;

Frm.GetCoord (&L &T, &R, &B); // правильно - функціяGetCoord()

// оголошена як const

 

Функцію-член класу Draw() можна також оголосити як const, оскільки вона не змінює значень змінних. Очевидно, має сенс додавати специфікатор const до всіх функцій-членів, які не модифікують поля класу, щоб користувач міг вільно викликати такі функції для об'єктів типу const. Звичайно, функцію, подібну CFrame::SetCoord(), не можна оголосити як const.

7.1.8 Статичні члени класу

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

Наприклад, в наступному класі оголошена статична змінна Count:

 

class CTest

{

public:

static int Count;

// залишок оголошення класу

};

Незалежно від того, скільки створюється об'єктів класу CTest, для змінної-члена Count існує єдина копія.

Окрім оголошення статичної змінної всередині класу, її необхідно також оголосити та ініціалізувати зовні, як глобальну змінну-член| класу.

При оголошенні статичних змінних поза класом необхідно вказувати клас, в якому вони оголошені, використовуючи оператор розширення області видимості (для нашого прикладу - CTest::). Наприклад, можна оголосити й ініціалізувати змінну Count таким чином:

 

// глобальні оголошення

int CTest::Count = 0;

 

Оскільки статичні змінні-члени класу існують незалежно від будь-якого об'єкту класу, до них можна звертатися, використовуючи ім'я класу і оператор розширення області видимості без посилання на об'єкт класу. Це демонструється в наступному прикладі:

 

void main ()

{

CTest::Count = 1;

// …

}

 

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

Можна також визначити функцію-член| класу з використанням специфікатора static, наприклад:

 

class CTest

{

// …

static int GetCount()

{

//тіло функції

}

// …

};

 

Статична функція-член класу має наступні властивості:

- Програмний код поза класом може викликати функцію з використанням імені класу і оператора розширення області видимості без посилання на об'єкт класу, який не потрібен, навіть якщо він існує, наприклад:

 

void main()

{

int Count = CTest::GetCount();

// …

}

 

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

Статичні змінні-члени класу можуть використовуватися як дані, які можуть змінюватися та які є спільні для всіх об'єктів класу. Наступна програма ілюструє використання статичних елементів для підрахунку поточної кількості об'єктів класу:

 

include <iostream>

using namespace std;

class CTest

{

private:

static int Count;

public:

CTest ()

{

++Count;

}

~CTest ()

{

--Count;

}

static int GetCount()

{

return Count;

}

};

 

int CTest::Count = 0;

void main ()

{

cout << CTest::GetCount () << " об'єктів існує n";

CTest Testl;

CTest *PTest2 = new CTest;

cout << CTest::GetCount () << " об'єкти існують n";

delete PTest2;

cout << CTest::GetCount () << " об'єкт існує n ";

}

 

В результаті на екран буде виведено наступне:

 

0 об'єктів існує

2 об'єкти існують

1 об'єкт існує

7.1.9 Вбудовані функції-члени класу

Якщо визначення функції-члена класу достатньо коротке, його можна включити в оголошення класу, роблячи тим самим функцію вбудованою (inline), тобто функцією, кожен виклик якої трактується як макрос – він замінюється тілом функції.

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

Наприклад, як показано нижче, функціяdivisible() за умовчанням стає вбудованою:

 

include <iostream>

using namespace std;

class CSamp

{

int i, j;

public:

CSamp(int а, int b); // оголошення конструктора

int divisible(){return !(i % j);}// функція, що вбудовується

// за умовчанням,

// оскільки дано її повне визначення

};

 

CSamp::CSamp(int а, int b) // визначення конструктора дано зовні

// класу, тому він не є вбудований

{

i = а;

j = b;

}

 

int main()

{

CSamp ob1(10, 2), ob2(10, 3);

if(ob1.divisible()) cout << "10 ділиться на 2n"; // буде видано

if(ob2.divisible()) cout << "10 ділиться на 3n"; // не буде видано

return 0;

}

 

Можна примусити компілятор розглядати функцію, задану поза оголошенням класу, як вбудовану. Для цього потрібно використати специфікатор inline при оголошенні функції всередині класу, а також при її визначенні поза класом. Наприклад, можна зробити всі функції-члени класу CStack вбудованими, оголосивши клас таким чином:

 

class CStack

{

char* v;

int top;

int max_size;

public:

class Underflow {}; // виключення (стек порожній)

class Overflow {}; // виключення (переповнення)

class Bad_size {}; // виключення (неправильний розмір)

CStack (int s) // конструктор

{

top = 0;

if (10000 < s) throw Bad_size();

max_size = s;

v = new char[s]; // розмістити елементи у вільній

// пам'яті (купі, динамічній пам'яті)

}

~CStack() // деструктор

{

delete[] v; // звільнити пам'ять для можливого

// подальшого використання

}

inline void push(char c);

inline char pop();

};

 

У цьому оголошенні конструктор і деструктор є вбудованими за умовчанням, оскільки вони визначені в оголошенні класу. Функції ж push() та pop() оголошені зі специфікатором inline. Із цим же специфікатором вони повинні бути й визначені поза оголошенням класу:

 

inline void CStack::push(char c)

{

if (top = = max_size) throw Overflow();

v(top)= c;

top = top + 1;

}

inline char CStack::pop()

{

if (top = = 0) throw Underflow();

top = top - 1;

return v(top);

}

 

Слід зазначити, що якщо специфікатор inline заданий у визначенні функції-члена класу, то в оголошенні цієї функції при оголошенні класу він може бути опущений.

7.1.10 Вказівник this

При посиланні на змінні-члени класу з коду, що знаходиться поза класом, у виразах завжди вказується об'єкт або вказівник на об'єкт класу. Отже, компілятор може визначити, копія змінних-членів класу якого об'єкту йому доступна.

Припустимо, наприклад, що клас CTest містить змінну N, тоді в приведеному нижче коді спочатку буде надрукована копія змінної N об'єкту Test1, а потім - об'єкту *PTest2:

 

CTest Testl;

CTest *PTest2 = new CTest;

//...

cout << Testl.N << 'n';

cout << Test2->N << 'n';

 

При посиланні на змінну-член усередині функції-члена класу конкретний об'єкт цього класу не задається:

 

class CTest {

public:

int N;

int GETN ()

{

return N; // N задається без вказівки на об'єкт

}

};

 

Як компілятор визначає, на копію змінної N якого об'єкту посилається метод класу? Відповідь полягає в наступному.

Компілятор передає методу класу прихований вказівник на об'єкт. Функція використовує саме цей прихований вказівник для доступу до коректної копії змінних-членів.

Наприклад, у виклику:

 

Test1.GetN();

 

компілятор передає функції GetN() прихований вказівник на об'єкт Test1. Функція GetN() використовує саме цей вказівник для доступу до копії змінної N, що належить об'єкту Test1.

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

Так, функціяGetN() може бути визначена таким чином:

 

int GetN()

{

return this->N; // еквівалентно return N;

}

Ім'я змінної-члена з виразом this-> перед ним є коректним, але не переслідує ніякої мети, оскільки використання вказівника this і так мається на увазі в простому посиланні на змінну-член класу|.

Якщо потрібно дістати доступ до глобальних змінних або до функції, що має таке ж ім'я, як змінна-член або функція-член, то перед ім'ям необхідно поставити оператор розширення області видимості "::", наприклад:

 

int N = 0; // глобальна змінна N

class CTest

{

public:

int N; // змінна-член класу N

int Demo()

{

cout << ::N << 'n'; // друк глобальної змінної N

cout << N << 'n'; // доступ до змінної-члена N

// з використанням вказівника this

}

};

7.1.11 Масиви об'єктів

У мові C++ масиви об'єктів можна оголошувати звичайним способом, наприклад:

 

CFrame FrmTable[10];

У цій інструкції CFrame — це клас, оголошений і розглянутий раніше.

Масив об'єктів також можна створити динамічно:

 

CFrame *FrmTable = new CFrame[10];

 

В обох варіантах при створенні масиву об'єктів компілятор викликає конструктор за умовчанням для кожного елементу масиву. Коли ж масив знищується, компілятор викликає деструктор для кожного такого елементу (мається на увазі, що деструктор визначений для класу). Якщо клас не має конструктора за умовчанням, виникне помилка.

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

Проте, якщо масив створений динамічно з використанням оператора new, не можна забезпечити ініціалізацію окремих елементів, оскільки компілятор завжди викликає конструктор за умовчанням для кожного елементу. Крім того, при знищенні динамічно створеного масиву потрібно використовувати оператор delete[], а не просто delete. Наприклад:

 

delete[] FrmTable;

 

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

Конструктори для елементів масиву викликаються в порядку зростання індексів елементів, а деструктори - в зворотному порядку.

7.1.12 Ієрархії об'єктів

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

Наприклад, клас COuter в наступному прикладі містить об'єкт-член класу CInner, ініціалізований в конструкторі класу COuter:

 

class CInner

{

// …

public:

CInner (int Parml, int Parm2)

{

// …

}

// …

};

 

class COuter

{

private:

CInner Inner;

public:

COuter (int P1, int P2, int P3): Inner(P1, P2)

{

// реалізація конструктора

}

// …

};

 

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

За допомогою включення одних об'єктів в інші можна моделювати досить складні й корисні ієрархічні системи, механізми, процеси і т.д.

7.1.13 Структури і об'єднання в мові C++

У мові C++ структури мови C були розширені таким чином, що в структури, так само як і в класи, стало можливим включати функції-члени, зокрема конструктори та деструктори.

Таким чином, єдиною відмінністю між структурою і класом в мові C++ стало те, що члени класу за умовчанням є закритими, а члени структури – відкритими.

Типове оголошення структури в мові C++ має наступний формат:

 

struct type_name {

// відкриті дані і функції – члени класу

private:

// закриті дані і функції – члени класу

};

 

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

Приклад структури з розширеними можливостями:

 

struct st_type

{

st_type(double b, char *n); // конструктор класу

void show(); // метод класу

private:

double balance; // закриті змінні-члени класу

char name[40];

};

 

У мові С++ об'єднання (union) також є різновидом класу, в якому функції та дані можуть міститися як його члени. Об'єднання схоже на структуру тим, що в ньому за умовчанням всі члени відкриті до тої точки, поки в оголошенні не зустрінеться специфікатор private. Головне ж тут полягає у тому, що в C++ всі змінні, які є членами об'єднання, розташовуються в пам'яті, починаючи з однієї і тієї ж адреси, тобто перекривають одна одну. Об'єднання C++ можуть мати конструктори і деструктори.

На об'єднання в C++ накладаються декілька обмежень.

По-перше, вони не можуть бути базовими класами і не можуть бути похідними класами, як це описується в наступному підрозділі.

По-друге, об'єднання не можуть мати статичних членів.

По-третє, об'єднання не можуть включати об'єкти класів, що мають конструктори або деструктори.

Наприклад:

 

union bits

{

bits(double n);

void Show_bits();

double d;

unsigned char c[sizeof(double)];

};

 

Природно, всі методи приведених як приклади структур та об'єднань повинні бути визначені зовні їх оголошень або ж усередині оголошень.

7.2 Похідні класи. Спадкування

Можна створити новий клас, породжений від уже існуючого, вказавши ім'я існуючого класу в оголошенні нового. Існуючий клас в цьому випадку називають базовим класом, а новий клас — похідним класом.

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

7.2.1 Похідні класи

Похідний клас успадковує всі члени базового класу. У похідний клас можна додати нові члени для його настройки відповідно до додаткових вимог.

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

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

Наприклад, CWindow можна оголосити таким чином:

 

class CWindow : public CFrame

{

// …

};

 

Язикова конструкція :public CFrame оголошує клас CWindow як похідний від CFrame. Клас CWindow успадковує усі змінні-члени й функції класу CFrame. Іншими словами, навіть якщо визначення класу CWindow у фігурних дужках буде порожнім, він вже містить функції GetCoord(), SetCoord() і Draw(), а також змінні Left, Top, Right і Bottom, оголошені в класі CFrame. При цьому CFrame буде базовим, а клас CWindow - похідним класом.

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

При створенні похідного класу, як правило, потрібно додати в нього нові члени, необхідні для задоволення специфічних вимог, що пред'являються до нового класу. Наприклад, оголошення класу CWindow можна представити таким чином:

 

class CWindow : public CFrame

{

private:

int FillColor; // зберігає колір, використовуваний для закрашення вікна

public:

void Draw (void)

{

int L, T, R, B;

CFrame::Draw ();

GetCoord (&L &T, &R, &B);

Fill ((L + R) / 2 (T + B) / 2, FillColor);

}

void SetColor (int Color)

{

FillColor = __max (0, Color);

}

};

 

Окрім успадкованих членів, CWindow містить закриту змінну FillColor для зберігання коду кольору для заливки вікна. Значення змінної FillColor встановлюється за допомогою нової відкритої функції-члена SetColor(). Клас CWindow також містить нову версію функції Draw() для малювання прозорих прямокутників з подальшою заливкою суцільним кольором.

Фактично клас CWindow включає дві версії функції Draw() - успадковану і спеціально визначену для похідного класу. При виклику функції Draw() для об'єкту класу CWindow викликається версія функції, визначена усередині класу CWindow, як показано в наступному фрагменті програми:

 

CWindow Wind;

// ...

Wind.Draw(); // виклик версії функції Draw(), визначеної всередині

// класу CWindow, оскільки об'єкт Wind має

// тип CWindow

 

Робота функції Draw(), визначеної для CWindow, починається з виклику версії функції Draw() класу CFrame, що малює прозорий прямокутник:

 

CFrame::Draw();

 

Використання в інструкції оператора розширення області видимості (CFrame::) приводить до виклику версії функції Draw(), що належить класу CFrame. Якщо ж даний оператор буде опущений, то компілятор згенерує рекурсивний виклик функції Draw(), визначеної для поточного класу CWindow. Це приклад того, як функція, визначена в рамках поточного класу, замінює собою успадковану функцію.

На наступному кроці Draw() викликає успадковану функцію GetCoord() для обчислення поточних значень координат прямокутника, а потім — функцію Fill() для заливки внутрішньої області прямокутника кольором, відповідним поточному коду кольору:

 

GetCoord (&L, &Т, &R, &В);

Fill ((L + R) /2, (Т + В) / 2, FillColor);

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

Приведемо фрагмент програми, в якому використовується клас CWindow:

 

CWindow Wind; // створення об'єкту класу CWindow

Wind.SetCoord (25, 25, 100, 100); // установка координат вікна

Wind.SetColor (5); // установка кольору вікна

Wind.Draw (); // виклик функції

// Draw() класу CWindow

7.2.2 Конструктори похідних класів

Конструктор похідного класу може явно ініціалізувати свій базовий клас шляхом передачі аргументів конструктору базового класу. Якщо конструктор похідного класу неявно ініціалізував базовий клас, компілятор автоматично викликає конструктор за умовчанням базового класу.

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

Щоб спростити використання класу CWindow, у відкриту частину оголошення класу необхідно додати конструктор для установки значень всіх змінних при створенні об'єкту класу:

 

CWindow (int L, int T, int R, int B, int Color):CFrame (L, T, R, B)

{

SetColor (Color);

}

 

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

Список ініціалізації конструктора похідного класу може використовуватися як для ініціалізації базового класу, так і для ініціалізації змінних-членів похідного класу.

Конструктор класу CWindow містить виклик функції SetColor(), яка встановлює значення змінної FillColor.

Тепер, використовуючи конструктор похідного класу, можна створити об'єкт класу CWindow і намалювати його всього двома інструкціями:

 

CWindow Wind (25, 25, 100, 100, 5);

Wind.Draw();

 

В оголошення класу можна також додати конструктор за умовчанням, що присвоює нульові значення всім змінним-членам класу:

 

CWindow ()

{

FillColor = 0;

}

 

Оскільки конструктор за умовчанням явно не ініціалізував базовий клас через те, що список ініціалізації порожній, компілятор автоматично викликає конструктор за умовчанням базового класу (CFrame::CFrame()), що присвоює нульові значення всім змінним, оголошеним усередині нього.

Якщо базовий клас не має конструктора за умовчанням, то компілятор видасть повідомлення про помилку.

Конструктор за умовчанням класу CWindow дозволяє створювати об'єкти, в яких всі змінні-члени рівні нулю, без додаткової передачі нульових значень або викликів методів класу:

 

CWindow Wind; // створений об'єкт класу CWindow, у якого всім

// змінним-членам присвоєні нульові значення

7.2.3 Порядок виклику конструкторів і деструкторів

При створенні об'єкту похідного класу компілятор викликає конструктори в наступному порядку:

- конструктор базового класу;

- конструктори всіх об'єктів-членів (тобто тих змінних-членів класу, які є об'єктами класів). Ці конструктори викликаються в порядку переліку об'єктів в оголошенні класу;

- власний конструктор класу.

Деструктори, якщо вони визначені, викликаються у зворотній послідовності.

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

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

7.2.4 Успадковані змінні

Навіть якщо клас CWindow успадковує змінні-члени Left, Top, Right і Bottom із свого базового класу, це не означає можливості прямого до них доступу, оскільки вони визначені в базовому класі як закриті. Без включення в класи додаткового спеціфікатора для доступу до змінних в похідному класі довелося б використовувати відкриту функцію GetCoord(), як і в других частинах програми. Таке обмеження доступу призводить до того, що код функцій-членів стає певною мірою неефективним.

Як альтернатива можна задати специфікатор доступу protected (захищений) замість private, як показано нижче:

 

// CFrm.h: заголовний файл класу CFrame

class CFrame

{

protected:

int Left;

int Top;

int Right;

int Bottom;

public:

CFrame ()

{

Left = Top = Right = Bottom = 0;

}

CFrame (int L, int T, int R, int B)

{

SetCoord (L, T, R, B);

}

void Draw ();

void GetCoord (int *L, int *T, int *R, int *B)

{

*L = Left;

*T = Top;

*R = Right;

*B = Bottom;

}

void SetCoord (int L, int T, int R, int B);

};

 

В даній версії класу CFrame передбачається визначення методів Draw() та SetCoord() поза класом. Подібно до закритих членів, до захищених членів класу доступ ззовні неможливий. Приведений нижче спосіб звернення до змінної є некоректним:

 

void main ( )

{

CFrame Frm;

Frm.Left = 10; // помилка - доступ до захищеного

// члену класу ззовні неможливий

}

 

Проте, на відміну від закритих членів класу, до захищених членів класу можна безпосередньо звертатися з класу, похідного від того класу, в якому такі члени класу оголошені. Отже, CWindow можна переписати, використовуючи прямий доступ до змінних-членів, оголошених в CFrame:

 

// CWindow.h: заголовний файл класу CWindow

#include "CFrm.h"

#include <сstdlib>

void Fill (int X, int Y, int Color);

class CWindow: public CFrame

(

protected:

int FillColor;

public:

CWindow ()

{

FillColor = 0;

}

CWindow (int L, int T, int R, int B, int Color):CFrame (L, T, R, B)

{

SetColor (Color);

}

void Draw ()

{

CFrame::Draw ( );

Fill ((Left + Right) / 2 (Top + Bottom) / 2, FillColor);

}

void SetColor (int Color)

{

FillColor =__max (0, Color);

}

};

 

В даному випадку змінну FillColor класу CWindow краще оголосити як захищену, а не як закриту. Це забезпечить доступ до неї з будь-яких класів, похідних від CWindow.

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