На головну

ГЛАВА 3. Основи об'єктно-орієнтованого програмування

  1. D. ОСНОВИ медичної мікології
  2. I. ФІЗИЧНІ ОСНОВИ КЛАСИЧНОЇ МЕХАНІКИ
  3. I.1.a.i.1. Застосування центральної концептуальної основи гнучким чином
  4. V2: Основи бухгалтерського обліку
  5. V2: Основи бухгалтерського обліку
  6. V2: Основи маркетингу

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

Описати клас можливо наступним чином:

class ім'я_класу {спісок_елементов};

Елементи каса (компоненти класу member) діляться на поля (дані-члени, елементи даних), які представляють собою дані і методи (компонентні функції, функції-члени), які представляють собою функції для роботи з даними.

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

Синтаксис опису методів класу в цілому відповідає синтаксису опису функцій. Метод можна оголосити як константний (метод, який не може змінювати значення полів класу). У цьому випадку вказується специфікатор const після списку параметрів. Рекомендується описувати як константні методи, які призначені для отримання значень полів.

Всі елементи класу мають певну область видимості. У класі можна використовувати один з наступних специфікаторів, керуючих видимістю елементів класу:

· Private (елементи видимі тільки всередині класу - приховані елементи),

· Public (елементи видимі як всередині так і поза класом - відкриті елементи - інтерфейс класу),

· Protected (елементи, які видимі лише всередині класу і спадкоємцям класу - захищені елементи).

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

наприклад,

class ім'я_класу

{

private:

опис прихованих елементів

public:

опис доступних елементів

};

Наведемо приклад опису класу «Рядок».

class CStr

{

char * s; // Поле для зберігання рядка

int len; // Поле для зберігання довжини рядка

public:

CStr () {len = 0; s = new char; * S = '\ 0';} // метод створення пустого рядка

CStr (char *); // Метод створення рядка, рівній заданій

char * get_str () const {return s;} // метод отримання рядка

int get_len () const {return len;} // метод отримання довжини рядка

}

В даному класі два прихованих поля і чотири доступних методу. Причому тіло одного з методів -CStr (char *) - не визначено всередині класу.

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

CStr :: Cstr (char * st)

{Len = strlen (st); s = new (char [len + 1]); strcpy (s, st); s [len] = '\ 0';}

Змінні, що мають тип описаного класу, прийнято називати об'єктами. Для опису об'єктів класу використовується одна з наступних конструкцій:

ім'я_класу імя_об'екта [(список параметрів)]; // Список не може бути порожнім

ім'я_класу (список параметрів); // Створюється об'єкт без імені, список може бути порожнім

ім'я_класу імя_об'екта = вираз; // Створюється об'єкт без імені і копіюється

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

СStr s1; // Створення об'єкта класу СStr - порожніх рядків

CStr s2 ( "aaa"); // Створення об'єкта класу СStr - рядки «aaa» з довжиною 3

CStr * s3 = & s2; // покажчик на об'єкт s2

CStr s4 = СStr ( "bbb"); // Створюється безіменний об'єкт зі значенням рядка рядка «bbb» і довжиною 3 і копіюється в створюваний об'єкт s4;

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

const CStr er ( "Error");

конструктор - Це спеціальний метод класу, ім'я якого збігається з ім'ям класу. Саме конструктор викликається автоматично при створенні об'єкта класу. У кожному класі є хоча б один конструктор. Якщо він не описаний програмістом, то створюється автоматично. Конструктор не повертає значення, навіть типу void і не успадковується. Конструктори не можна описувати з специфікаторами const, virtual, static. Клас може містити кілька конструкторів з різними типами параметрів. Конструктор без параметрів або конструктор, всі параметри якого мають значення за замовчуванням, називають конструктором за замовчуванням. Параметри конструктора можуть мати будь-який тип крім типу цього ж класу. Один з конструкторів може мати значення параметрів за замовчуванням. При завданні декількох конструкторів слід дотримуватися тих самих правил що і при описі перевантажених функцій - у компілятора повинна бути можливість розпізнати потрібний конструктор по типу параметрів.

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

CStr (): len (0) {s = new char; * S = '\ 0';}

Спеціальним видом конструктора є конструктор копіювання. Його єдиним параметром є покажчик на об'єкт цього ж класу:

ім'я класу (const ім'я класу &) {тіло}

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

Приклад конструктора копіювання для класу CStr

CStr :: CStr (const CSrt & А)

{

len = strlen (A.s);

s = new char [strlen (A.s) +1];

strcpy (s, A.s);

}

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

Приклад деструтора для класу CStr

СStr :: ~ Csrt () {delete [] s};

Доступ до елементів класу здійснюється зазвичай за допомогою операції уточненого імені

ім'я об'єкта. ім'я елемента,

наприклад,

cout << s3.get_str () << s3.get_len ();

Якщо визначено покажчик на об'єкт, то можна використовувати операцію ->, наприклад

cout << s4-> get_str () << s4-> get_len ();

Усередині кожного методу неявним чином використовується покажчик this - це константний покажчик на об'єкт, що викликав метод. Він передається в метод як прихований параметр. У явному вигляді покажчик this застосовується в основному для повернення з методу покажчика (return this) або посилання (return * this) на що викликав метод об'єкт. Наприклад, розглянемо метод, що порівнює довжину двох рядків і повертає рядок, що має максимальну довжину.

CStr & long (CStr & A)

{

if (len> A.get_len ()) return * this;

return A;

}

Приклад виклику методу:

CStr a ( "aaaa"), b ( "bbb");

CStr max = a.long (b);

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

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

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

Синтаксис опису функції-операції

тип operator операція (список параметрів) {тіло}

Наприклад, опишемо операцію видалення з рядка останнього символу

class CStr

{... CStr & operator - () {s [len-1] = '\ 0'; --len; return * this;}};

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

class CStr

{Bool operator = = (const CStr & st)

{If (strcmp (s, st.get_str ()) == 0) return true; return false; }

}

Наведемо приклад функції -Операції, що є дружньою двох класів.

friend ostream & operаtor << (ostream & out, CStr & st)

{Return out << st.s;}

Таким чином, нами описаний следущий клас CStr

class CStr

{

protected:

char * s; int len;

public:

CStr (); CStr (char *);

CStr (char); CStr (const CStr &);

CStr & operator = (const CStr &);

bool operator == (CStr &);

void empty ();

operator int () {return len;}

~ CStr () {delete [] s; cout << "\ nDestructor!";}

char * get_str () const {return s;} int get_len () const {return len;}

friend ostream & operator << (ostream &, CStr &);

}

// Конструктор створення пустого рядка

Str :: CStr (): len (0)

{S = new char; * s = '\ 0'; cout << "\ nContructor1";}

// Конструктор створення рядка, рівній заданій С- рядку

CStr :: CStr (char * a)

{S = new char [len = strlen (a)];

strcpy (s, a);

cout << "\ nContructor2";

}

// Конструктор створення рядки з одного символу

CStr :: CStr (char a)

{S = new char [len = 2]; s [0] = a; s [1] = '\ 0'; cout << "\ nContructor3";}

// Конструктор копіювання

CStr :: CStr (const CStr & a)

{S = new char [len = a]; strcpy (s, a.s); cout << "\ nContructor4";}

// Операція присвоювання

CStr & CStr :: operator = (const CStr & a)

{

if (& a == this) return * this;

if (len) delete [] s;

s = new char [len = a];

strcpy (s, a.s);

cout << "\ nDONE ==";

return * this;

}

// Операція порівняння рядків

bool CStr :: operator == (CStr & st)

{

if (strcmp (s, st.s) == 0) return true;

return false;

}

// Метод, що робить рядок порожній

void CStr :: empty ()

{If (len)

{Len = 0; delete [] s; s = new char; * S = '\ 0';}

}

// Операція записи в потік виводу на екран

ostream & operator << (ostream & a, CStr & x)

{Return a << x.s;}

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

Синтаксис опису класу-спадкоємця

сlass ім'я: [ключ доступу] ім'я базового класу {тіло класу};

Ключ доступу може мати одне з трьох значень private, protected, public

Ключ доступу private (захищене успадкування, діє за замовчуванням) - знижує статуси доступу public і protected елементів базового класу до private. Ключ доступу public (відкрите успадкування) - не змінює статусу доступу елементів базового класу. Ключ доступу рrotected (захищене успадкування) знижує статус доступу public елементів базового класу до protected.

Наприклад, створимо похідний клас CBStr від базового класу CStr, призначений для зберігання бінарних рядків.

class CBStr: public CStr

{Public:

CBStr ();

CBStr (char * a);

CBStr & operator = (const CBStr &);

CBStr operator + (const CBStr &);

void empty ();

operator int ();

};

Розглянемо поля і методи похідного класу.

Всі поля базового класу успадковуються.

Якщо поля батьків мають тип private, то для роботи з ними в класі - спадкоємця необхідно використовувати методи базового класу або оголосити їх явно в спадкоємця в секції public наступним чином ім'я базового_класса :: ім'я_поля. Наприклад, якби поля s, len були б описані в класі CStr як private, то в класі CBStr їх слід оголосити в такий спосіб:

class CBStr: public CStr

{

....

public:

СStr :: s;

СStr :: len;

....

};

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

Для різних методів класу існує різні методи спадкування. Успадковуються всі методи, крім конструкторів, деструкторів і операції привласнення.

Тобто клас CBStr успадковує методи empty (), operator ==, operator int (), get_str (), get_len (), і та дружню функцію-оператор operator <<;

Однак, як ми бачимо в класі CBStr методи empty (), operator int () перевизначені.

// Метод, що робить рядок порожній

void CBStr :: empty ()

{If (len)

{Delete [] s;

len = 1;

s = new char [2];

s [0] = '0';

s [1] = '\ 0';

}

}

// Функція-операція перетворення типу, яка повертає десяткове значення двійковій рядки

CBStr :: operator int ()

{

int k = s [len-1] -48;

int st = 2;

for (int i = len-2; i> = 0; i--)

{K + = ((s [i] -48) * st); st * = 2;}

return k;

}

Крім того, в класі CBStr визначено новий метод

// Операція складання двох двійкових чисел

CBStr CBStr :: operator + (const CBStr & a)

{

int l;

if (len> a.len) l = len; else l = a.len;

char * str = new char [l + 2];

itoa (int (* this) + int (a), str, 2);

CBStr S (str);

delete str;

return S;

}

Опредлелім конструктори класу

Припустимо, що створення порожній бінарної рядки рівносильно створенню звичайної рядка, що складається з одного символу "0". Тоді конструктор похідного класу повинен викликати конструктор базового класу з параметром '0':

CBStr (): CStr ( '0') {}

Конструктор бінарної рядки, рівній заданій З-рядку, повинен викликати конструктор і якщо створена рядок, містить символи відмінно від 0 і 1, робити рядок порожній

CBStr :: CBStr (char * a): CStr (a) {if (! Bin (a)) empty ();}

{If (! Bin (a)) empty (); }

де bin () - функція перевірки С-рядки на бинарность, empty () - метод, який робить рядок порожній.

bool bin (char * a)

{

int i = 0;

while (a [i])

{If (a [i]! = '0' && a [i]! = '1') return false;

i ++;

}

return true;

}

Конструктор копіювання створиться автоматично і викличе конструктор копіювання базового класу.

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

Так як операція присвоювання НЕ наследуеются, її необхідно явно перевизначити

CBStr & CBStr :: operator = (const CBStr & a)

{CStr :: operator = (a);}

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

Розглянемо приклад роботи з об'єктами однієї ієрархії через покажчики.

CStr a ( "aaa");

CBStr b ( "101");

CStr * p1 = & a;

CBStr * p2 = & b;

cout << "\ na =" << a << "" << int (a);

cout << "\ nb =" << b << "" << int (b);

p1-> empty ();

p2-> empty ();

cout << "\ na =" << a << "" << int (a);

cout << "\ nb =" << b << "" << int (b);

CBStr c ( "1011");

CStr * p3 = & c;

cout << "\ nc =" << c << "" << int (c);

p3-> empty ();

cout << "\ nc =" << c << "" << int (c);

Ця програма виведе на екран

a = aaa 3

b = 101 5

a = 0

b = 0

c = 1011 11

c = -48

Як ми бачимо, для об'єкта, на який посилається вказівник p3, був викликаний метод empty () базового класу CStr, що семантично невірно. Таким чином, перевизначення метод empty () похідного класу виявився недоступним. Це відбувається через те, що компілятор не може передбачити на об'єкт якого класу буде фактично посилається покажчик під час виконання програми і вибирає завжди метод базового класу. Щоб уникнути цієї ситуації необхідно оголосити в баовом класі метод empty () як віртуальний, тобто зі специфікатором virtual.



Завдання 3. Використання стеків і черг | Завдання 1 . Опис найпростішого класу

Завдання 6. посимвольного обробка рядків | Завдання 7. Сортування масиву | функції | Завдання 1. Визначення і виклик функцій | Завдання 2. Рекурсивні функції | Завдання 3. Використання бібліотечних функцій string.h | Завдання 4. Використання бібліотечних функцій stdio.h | ГЛАВА 2. Динамічні структури даних | Завдання 1. Структури | Завдання 2. Динамічний список |

© um.co.ua - учбові матеріали та реферати