Главная » Программирование звука » Устоявшиеся соглашения

0

Прежние  объектно-ориентированные  языки  не  пользовались  особой  популярностью  из-за  проблем  с  эффективностью.  Так  как  C++  разрабатывался,  в  частности,  для  разрешения  этих  проблем,  в  него  было  включено  множество  деталей, позволяющих писать более эффективный код.

Инициализация конструктора

Рассмотрим  мой  пример  буфера.  Резонно  представить,  что  вы  захотите  задавать  размер  при  создании  буфера.  Один  из  способов  реализовать  это   добавить новую  функцию-член,  позволяющую  производить  изменения  размера  буфера  после создания объекта. Это снизило бы эффективность, поэтому C++ и здесь приходит вам на помощь и разрешает применять несколько конструкторов.

B  моем  примере  буфера  я,  вероятно,  задал  бы  и  стандартный  конструктор

(default constructor) тот, который не принимает аргументов:

Buffer() { _data = new char [256]; }

и другой конструктор, который примает аргумент:

Buffer(int i) { _data = new char [i]; }

Теперь у меня есть два способа создания нового буфера. Как и раньше, я могу создать стандартный буфер либо в стеке, либо в динамической памяти:

Buffer myBuff;

Buffer *pBuff = new Buffer;

A могу создать особый буфер, указав размер:

Buffer myBuff(1024);

Buffer *pBuff = new Buffer (1000);

Хотя  это  выглядит  слегка  похожим  на  вызов  функции,  в  действительности  это   особый   способ   инициализации   переменной.   Используя   данную   запись,   вы можете так же инициализировать встроенные типы:

int i=3;    // Инициализация  по-старому. int j(4);   // Инициализация  по-новому.

Новый  способ  инициализации  в  некоторых  случаях  имеет  небольшое  преимущество  в  эффективности.  (Если  вы  используете  старый  способ,  некоторые  компиляторы  создают  временный  объект,  затем  копируют  его  в  переменную,  а  затем удаляют  временный  объект;  при  использовании  новой  записи  всегда  происходит инициализация объекта непосредственно в переменной.)

Использование

Обычно вы ссылаетесь на функцию-член, начиная запись с имени объекта. Иногда, однако, вам требуется явно указать, какому классу принадлежит нужная функциячлен. Это происходит при определении функций, о котором я расскажу чуть позднее, и в том случае, если вам необходимо временно обойти обычные правила наследования. Наряду с записью object.Foo() или objectPointer->Foo() вы можете использовать запись Class::Foo() или даже object.Class::Foo(), хотя последняя форма несколько необычна.

Типичный  пример  подобного  использования  представлен  в  главе  21,  в  определении   PlayNote::SamplingRate (то   есть,   член   SamplingRate класса PlayNote). B  этом  случае класс  PlayNote хочет не  замещать,  а расширить  определение   AudioAbstract::SamplingRate.   Он   реализует   подобный   подход, сначала  вызывая  родительскую  версию,  а  затем  проводя  любую  дополнительную обработку.

Определение методов в отдельных файлах

Программисты C выработали соглашение о разделении их кода на модули. Каждый  модуль  состоит  из  интерфейса  (interface),  хранимого  в  (заголовочном)  файле с   расширением   .h,   и   реализации   (implementation),   сохраняемой   в   (исходном) файле с расширением .с. Это делается для ускорения компиляции: компилятор

работает быстрее, если читает только заголовочные файлы. B моем примере класса Buffer я разместил все функции-члены в определении класса, однако профессиональный код записывается не так.

Обычно   определение   класса   содержит   только   объявления   функций-членов, конструкторов  и  деструкторов.  Ha  самом  деле  мой  пример  в  заголовочном  (.h) файле выглядел бы приблизительно так:

class Buffer {

private:

char *_begin; char *_end; char *_data;

public: Buffer() ;

~Buffer ();

void Insert (char а);

char Remove ();

};

Исходный  файл  .cpp (содержащий  реализацию)  включал  бы  в  себя  непосредственно тела функций. Конечно, может быть много классов, содержащих функцию-член   Insert(),   поэтому   необходимо   использовать   соответствующую   запись с оператором :: для выбора нужной функции-члена. B этом случае в файле исходного программного кода функции выглядели бы так:

Buffer(){ _begin = _data; _end = _data; }

~Buffer () { delete[] _data;}

void Insert (char а) { *_end++ = а ;}

char Remove () {return *_begin++ ; }

Заметим, что конструкторы и деструкторы не имеют возвращаемого типа.

Подобное   разделение   создает   незначительные   неудобства   при   поддержке файлов;   вам   следует   постоянно   следить   за   тем,   чтобы   объявления   в   вашем

.h-файле  и  определения  в  .срр-файле  были  непротиворечивы.  K  счастью,  большинство  компиляторов  с  готовностью  укажут  вам  на  любые  появляющиеся  противоречия.

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии