Істинне ООП: класи і спадкування

Тепер я готовий навести приклад з буфером, використовуючи звичайну для C + + запис:

class Buffer {

private:

char *_begin

char *_end

char _data[256]

public:

Buffer() { _begin = _data _end = _data }

~Buffer () { delete[] _data}

void Insert (char a) { *_end++ = a }

char Remove () {return *_begin++ }

}

Тут я дещо змінив По-перше, використовував слово class замість struct Хоча ці два слова майже взаємозамінні в C + +, за традицією використовується слово class для усього, що містить функції-члени

Я також додав специфікатори видимості Елементи private: і public: кажуть компілятору, що певні члени цього класу можна використовувати поза його, а деякі ні Зауважте, що я приховав всі члени даних Це вважається хорошим стилем програмування

Приватні дані вважаються хорошим стилем програмування, тому що вони спрощують здійснення зміни способу зберігання даних Навіть у такому простому прикладі я змінив тип змінної _data з масиву на покажчик і додав програмний код для розміщення буфера в купі Якщо я проводжу виділення памяті під буфер, мені необхідно гарантувати, що ця память буде звільнена, тому я додав деструктор (Destructor) Деструктор має те ж імя, що й клас, але воно починається з тильди (~ Buffer) Компілятор викликає деструктор автоматично на відповідні моменти часу (під час виконання оператора delete або наприкінці блоку для автоматичних змінних)

Так як сам буфер є масивом, я використовував форми new і delete,

призначені для роботи з масивами Запис new char [256] розміщує

в памяті блок з 256 символів оператор delete [] _data звільняє блок

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

даної форму запису оператора delete для її звільнення Деякі компілятори за цим не стежать, і ви, якщо будете неуважні, можете переповнити купу

Спадкування

Більшість програм створюються поступово Спочатку ви розробляєте ядра, що володіють базовою функціональністю, а потім прикрашаєте програму безліччю невеликих доповнень Як і всі інші обєктно-орієнтовані мови, C + + явним чином підтримує цю особливість черезспадкування (inheritance)

Спадкування використовується двома способами Перший уточнення іншого класу Наприклад, якщо вам потрібен буфер, що володіє деякими додатковими можливостями, доречно створити новий клас SpecialBuffer, що успадковує від Buffer:

class SpecialBuffer :public Buffer {

/ / Нові властивості SpecialBuffer

}

Запис: public Buffer показує: SpecialBuffer дeлaeт вce, щo yмeeт Buffer B Зокрема, SpecialBuffer автоматично отримує всі переменниечлени і функції-члени класу Buffer

Спадкування надзвичайно корисний інструмент при розробці про-

граммного забезпечення Наприклад, при проектуванні своїх класів-програвачів я знав, що різні типи програвачів мають спільно володіти певними можливостями Визначивши ці можливості один раз в загальному базовому класі(Base class), я зміг спростити класи специфічних програвачів A оскільки існувала всього одна копія спільно розділяється коду, всю програму було легше налагоджувати і підтримувати

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

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

Зазвичай, коли ви використовуєте запис objectFoo (), компілятор звертає увагу на тип змінної object і вибирає потрібну функцію Foo () під час компляціі Однак це не завжди те, що ви хочете

Припустимо, у вас є кілька класів із загальним інтерфейсом Наприклад, обидва моїх класу WaveRead і MidiRead успадковують від AudioAbstract, тому ви можете викликати метод GetSamples для будь-якого з них Ha насправді, варто лише подивитися на мій код, як стане очевидно, що більша його частина працює тільки на те, щоб обєкт успадковував від AudioAbstract B таких випадках я використовую покажчики на AudioAbstract Розглянемо наступний невеликий приклад:

MidiRead audioObject / / Створюємо обєкт MidiRead AudioAbstract * audioPointer = & audioObject

audioPointer-> GetSamples () / / Запитуємо відліки

Зазвичай компілятор дивиться на тип змінної audioPointer B залежності від використовуваного типу, він перетворює останню сходинку в виклик методу GetSamples класу AudioAbstract Це не зовсім вірно Рішення полягає в тому, щоб помітити методи, подібні GetSamples, специфікатором virtual Оголошення функції з таким специфікатором змушує компілятор перевіряти тип обєкта під час виконання програми і використовувати цю інформацію для вибору підлягає викликом методу GetSamples (Віртуальні методи лише трохи менш ефективні в порівнянні з невіртуальними Компілятор будує таблицю адрес функцій для кожного класу і одного разу здійснює пошук по масиву для визначення місцезнаходження віртуальної функції Втрата ефективності настільки невелика, що у багатьох програмістів увійшло в звичку визначати будь-яку функцію-член як віртуальну)

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

MIDI використовує класи, які успадковують від AbstractNote Це дозволяє їм застосовувати безліч методів інструментного синтезу без будь-яких змін програмного коду MIDI

Існує кілька невеликих складнощів, повязаних з використовуваною в C + + формою спадкування Одна полягає в тому, що деструктори завжди повинні бути віртуальними B іншому випадку при видаленні обєкта може бути викликаний не той деструктор (K щастя, специфікатор virtual успадковується, тому досить оголосити конструктор як virtual у базовому класі)

Унаслідок тісного звязку між деякими основними принципами C + + важ-

але розуміти відмінності міжкласами визначень(Concrete) іабстрактними (Abstract) класами Якщо бути коротким, то на основі першого ви створюєте обєкти на основі другого ви їх ніколи не створите Наприклад, ви ніколи не побачите, щоб я створював обєкт класу AudioAbstract (я визначаю багато покажчиків на AudioAbstract, але всі вони вказують на обєкти похідних класів) Два важливих правила C + + полягають у наступному:ніколи не наслідувати від класу визначень і ніколи не створювати обєкти абстрактного класу

B моєму коді абстрактні класи як частина свого імені містять слово

Abstract

Це допомагає мені завжди бути впевненим, що я ніколи не створюю обєкти абстрактно-

го класу і завжди успадковують від абстрактного класу

Є спосіб підкреслити це розходження, використовуючи віртуальні функції Якщо вони визначаються зі специфікатором = 0, компілятор не допустить створення вами обєкта даного класу Такі функції називаютьсячистими віртуальними функціями(Pure virtual function), і багато визначають абстрактний клас як клас, що містить, принаймні, одну чисту віртуальну функцію Однак це визначення здається мені зайво обмежуючим головним чином тому, що абстрактні класи і класи визначень в інших обєктно-орієнтованих мовах являють собою широковживаних концепції проектування

Джерело: Кінтцель Т Керівництво програміста по роботі зі звуком = A Programmers Guide to Sound: Пер з англ М: ДМК Пресс, 2000 432 с, іл (Серія «Для програмістів»)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*