logo
Языки программирования

15.3. Данные класса

Конструкторы и деструкторы

Конструктор (constructor) — это подпрограмма, которая вызывается, когда создается объект класса; когда объект уничтожается, вызывается деструктор (destructor). Фактически, каждый объект (переменная), определенный в ка­ком-либо языке, требует выполнения некоторой обработки при создании и уничтожении переменной хотя бы для выделения и освобождения памяти. В объектно-ориентированных языках программист может задать такую обра­ботку.

Конструкторы и деструкторы в языке C++ могут быть определены для лю­бого класса; фактически, если вы не определяете их сами, компилятор обес­печит предусмотренные по умолчанию. Синтаксически конструктор — это подпрограмма с именем класса, а деструктор — то же имя с префиксным сим­волом «~»:

class Airplanes {

private:

C++

Airplane_Data database [100];

int current_airplanes;

public:

Airplanes(int i = 0): current_airplanes(i) {};

~Airplanes();

};

После создания базы данных Airplanes число самолетов получает значение па­раметра i, который по умолчанию имеет значение ноль:

Airplanes а1 (15); // current_airplanes =15

Airplanes a2; //current_airplanes = О

Когда база данных удаляется, будет выполнен код деструктора (не показанный). Можно определить несколько конструкторов, которые перегружаются на сигнатурах параметров:

class Airplanes {

public:

Airplanes(int i = 0): current_airplanes(i) {};

C++

Airplanes(int i, int j): current_alrplanes(i+j) {};

~Airptartes();

};

Airplanes a3(5,6); // current_airplanes = 11

В языке C++ также есть конструктор копирования (copy constructor), который дает возможность программисту задать свою обработку для случая, когда объ­ект инициализируется значением существующего объекта или, в более общем случае, когда один объект присваивается другому. Полное определение кон­структоров и деструкторов в языке C++ довольно сложное; более подробно см. гл. 12 справочного руководства по языку C++.

В языке Ada 95 явные конструкторы и деструкторы обычно не объявля­ются. Для простой инициализации переменных достаточно использовать зна­чения по умолчанию для полей записи:

type Airplanes is tagged

record

Current_Airplanes: Integer := 0;

end record;

Ada


или дискриминанты (см. раздел 10.4):

type Airplanes(lnitial: Integer) is tagged

record

Current_Airplanes: Integer := Initial;

end record;

Программист может определить свои обработчики, порождая тип из абстрак­тного типа, называемого управляемым (Controlled). Этот тип обеспечивает аб­страктные подпрограммы для Инициализации (Initialization), Завершения (Finalization) и Корректировки (Adjust) для присваивания, которые вы можете заместить нужными вам программами. За деталями нужно обратиться к пакету Ada. Finalization, описанному в разделе 7.6 справочного руководства по языку Ada.

Class-wide-объекты

Память распределяется для каждого экземпляра класса:

C++

class С {

chars[100];

};

С с1,с2; //по 100 символов для с1 и с2

Иногда полезно иметь переменную, которая является общей для всех экземп­ляров класса. Например, чтобы присвоить порядковый номер каждому экзем­пляру, можно было бы завести переменную last для записи последнего при­своенного номера. В языке Ada это явно делается с помощью включения обычного объявления переменной в теле пакета:

package body P is

Last: Integer := 0;

Ada

end P;

в то время как в языке'C++ нужно воспользоваться другим синтаксисом:

class С {

C++

static int last; //Объявление

chars[100];

};

int C::last = 0; // Определение, доступное за пределами файла

Спецификатор static в данном случае означает, что будет заведен один CW-объект*. Вы должны явно определить компонент static за пределами определе­ния класса. Обратите внимание, что статический (static) компонент класса имеет внешнее связывание и может быть доступен из других файлов, в отли­чие от статического объявления в области файла.

Преобразование вверх и вниз

В разделе 14.4 мы описали, как в языке C++ значение порожденного класса может быть неявно преобразовано в значение базового класса. Это называет­ся преобразованием вверх (up-conversion), потому что преобразование делается вверх от потомка к любому из его предков. Это также называется сужением (narrowing), Потому что производный тип «широкий» (так как он имеет допол­нительные поля), в то время как базовый тип «узкий», он имеет только поля, которые являются общими для всех типов в производном семействе. Запом­ните, что преобразование вверх происходит только, когда значение произ­водного типа непосредственно присваивается переменной базового типа, а не когда указатель присваивается от одной переменной другой.

Преобразование вниз (down-conversion) от значения базового типа к значе­нию производного типа не допускается, поскольку мы не знаем, какие значе­ния включить в дополнительные поля. Рассмотрим, однако, указатель на ба­зовый тип:

Base_Class* Base_Ptr = new Base_Class;

C++

Derived_Class* Derived_Ptr = new Derived_Class;

if (...) Base_Ptr = Derived_Ptr;

Derived_Ptr = Base_Ptr; // На какой тип указывает Base_Ptr?

Конечно, возможно, что Base_Ptr фактически укажет на объект производно­го типа; в этом случае нет никакой причины отклонить присваивание. С дру­гой стороны, если указуемый объект фактически имеет базовый тип, мы дела­ем попытку преобразования вниз, и присваивание должно быть отвергнуто. Чтобы предусмотреть этот случай, в языке C++ определено динамическое пре­образование типов (dynamic cast), которое является условным в зависимости от типа указуемого объекта:

C++

Derived_Ptr = dynamic_cast<Derived_Class*>Base_Ptr;

Если указуемый объект фактически имеет производный тип, преобразование завершается успешно. В противном случае указателю присваивается 0, и про­граммист может это проверить.

Уже в языке Ada 83 допускалось явное преобразование между любыми дву­мя типами, порожденными друг из друга. Это не вызывало никаких проблем, потому что производные типы имеют в точности те же самые компоненты. Для них допустимо иметь различные представления (см. раздел 5.8), но пре­образование типов совершенно четко определено, потому что оба представле­ния имеют одинаковые число и типы компонентов.

Расширение преобразования производного типа до теговых типов не вызывает проблем в случае преобразования вверх от производного типа к ба­зовому. Ненужные поля усекаются:

Ada

S:SST_Data;

A: Airplane_Data := Airplane_Data(S);

В другом направлении используются агрегаты расширения (extention aggregates), чтобы обеспечить значения для полей, которые были добавлены при расширении:

Ada

S:=(AwithMach=>1.7);

Поля Speed и подобные берутся из соответствующих полей в значении А, а дополнительное поле Mach задано явно.

При попытке преобразования вниз CW-типа к конкретному типу делается проверка во время выполнения, и, если CW-объект не производного типа, произойдет исключительная ситуация:

Ada

I Ada procedure P(C: Airplane_Data'Class) is

S:SST_Data;

begin

S := SST_Data(C); - Какой тип у С ??

exception

when Constraint_Error => .. .

end P;