Порядок вызова конструкторов и деструкторов

Конструкторы производных классов

Производные классы

Статические члены класса

Обычно экземпляр класса имеет собственную копию переменных-членов, принадлежащих классу. Однако, если переменная-член объявлена с использованием спецификатора 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.h> 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 () << " objects exist\n";CTest Testl;CTest *PTest2 = new CTest;cout << CTest::GetCount () << " objects exist\n"; delete PTest2;cout « CTest::GetCount 0 « " objects exist\n " }

В результате будет напечатано следующее.

0 objects exist2 objects exist1 objects exist

Допустим, что класс CRectangle уже написан, отлажен и используется в программе. Предположим также, что кроме прозрачных прямоугольников вам потребовалось отображать и сплошные блоки (прямоугольники, залитые сплошным цветом). Для этого необходимо определить новый класс. Назовем его, например, CBlock. Он будет содержать большую часть свойств класса CRectangle и некоторые дополнительные средства для заливки нарисованных прямоугольников.

Если создавать СВlock как полностью новый класс, придется продублировать большую часть всего написанного ранее для CRectangle. К счастью, средства языка C++ позволяют избежать дублирования кода и данных благодаря возможности создавать новые классы как производные от существующих. Создаваемый производный класс наследует все данные и функции, принадлежащие существующему классу.

Например, CBlock можно определить следующим образом.

class CBlock : public CRectangle{};

Выражение: public CRectangle обозначает класс CBlock как производный от CRectangle. Класс CBlock наследует все переменные-члены и функции CRectangle. Другими словами, несмотря на то, что определение класса CBlock является пустым, он уже содержит функции GetCoord, SetCoord и Draw, а также переменные Left, Top, Right и Bottom, определенные в классе CRectangle. При этом CRectangle называют базовым, а класс CBlock — производным классами.

Совет

В общем случае спецификатор public следует включать в первую строку определения производного класса, как показано в приведенном примере. Использование данного спецификатора приводит к тому, что все открытые члены базового класса (объявленные как public), остаются открытыми в производном классе.

После создания производного класса требуется добавить в него новые члены, необходимые для удовлетворения специальных требований, предъявляемых к новому классу. Например, определение класса CBlock можно представить следующим образом.

class CBlock : public CRectangle{ private: int FillColor; //хранит цвет, используемый для закрашивания блока public: void Draw (void) { int L, T, R, B; CRectangle::Draw (); GetCoord (&L, fiT, &R, &B) ; Fill ((L + R) /2, (T + B) /2, FillColor); } void SetColor (int Color) { FillColor = __max (0, Color); } };

Кроме наследуемых членов, CBlock содержит закрытую переменную FillColor для хранения цветового кода заливки блока. Значение переменной FillColor устанавливается с помощью новой открытой функции-члена SetColor, Класс CBlock также содержит новую версию функции Draw для рисования прозрачных прямоугольников с последующей заливкой сплошным цветом.

Фактически класс CBlock включает две версии функции Draw — наследуемую и определенную явно. При вызове функции Draw для объекта типа CBlock версия функции, определенная внутри CBlock, переопределяет функцию, определенную внутри CRectangle, как показано в следующем фрагменте программы.

CBlock Block; // ... Вlock.Draw (); // вызов версии функции Draw, определенной внутри // класса CBlock, так как объект Block имеет тип CBlock

Примечание

Далее в параграфе "Применение виртуальных функций" рассмотрен более эффективный способ задания и переопределения функций класса.

Версия функции Draw, определенная для CBlock, начинается с вызова версии функции Draw класса CRectangle, рисующей прозрачный прямоугольник.

CRectangle::Draw();

Использование выражения с оператором расширения области видимости (CRectangle::) приводит к вызову версии функции Draw, принадлежащей классу CRectangle. Если же данное выражение будет опущено, то компилятор сгенерирует рекурсивный вызов функции Draw, определенной для текущего класса CBlock. Это пример того, как функция, определенная в рамках текущего класса, заменяет собой наследуемую функцию.

На следующем этапе Draw вызывает наследуемую функцию GetCoord для вычисления текущих значений координат прямоугольника, а затем – функцию Fill для заливки внутренней области прямоугольника цветом, соответствующим текущему цветовому коду.

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

Fill – это гипотетическая функция заливки замкнутой области. Первые два параметра задают координаты точки внутренней области, а третий — цветовой код, используемый при заливке этой области. Такая функция может быть определена в любом месте программы или вызвана из библиотеки функций.

Приведем фрагмент программы, в котором используется класс CBlock.

CBlock Block; // создание экземпляра класса CBlockBlock.SetCoord (25,25,100,100); // установка координат блокаBlock.SetColor (5); // установка цвета блокаBlock.Draw (); // вызов функции Draw класса CBlock

Обратите внимание: программа с помощью класса CBlock сначала вызывает функции-члены SetCoord и SetColor для установки значений переменных класса, а затем вызывает функцию Draw для рисования блока. Чтобы упростить использование CBlock, необходимо добавить конструктор для установки значений всех переменных при создании экземпляра класса.

CBlock (int L, int T, int R, int B, int Color) : CRectangle {L, T, R, B) { SetColor (Color);}

Список инициализации членов класса в данном конструкторе содержит вызов конструктора базового класса CRectangle, которому передаются значения, присваиваемые переменным-членам. Список инициализации может использоваться для инициализации как базового класса, так и переменных членов (рассматривался в параграфе «Инициализация переменных-членов в конструкторах» гл. 3). Конструктор класса CBlock содержит вызов функции SetColor, которая устанавливает значение переменной FillColor. Теперь, используя конструктор класса, можно создать объект класса CBlock и задать блок всего двумя операторами.

CBlock Block (25, 25, 100, 100, 5}; Block.Draw ( );

Можно также добавить конструктор по умолчанию, присваивающий нулевые значения всем переменным-членам класса.

CBlock () { FillColor = 0;}

Так как конструктор по умолчанию явно не инициализирует базовый класс (список инициализации пуст), компилятор автоматически вызывает конструктор по умолчанию базового класса (CRectangle::CRectangle ( )), присваивающий нулевые значения всем переменным, определенным внутри него. Если базовый класс не имеет конструктора по умолчанию, то будет получено сообщение об ошибке.

Конструктор по умолчанию класса CBlock позволяет создавать объекты, в которых все переменные-члены равны нулю, без передачи значений или вызова методов класса.

CBlock Block; // создается объект класса CBlock, у которого всем // переменным-членам присвоены нулевые значения

В конце следующего параграфа приведен листинг 4.2 – полный текст класса CBlock с двумя новыми конструкторами.

При создании экземпляра производного класса компилятор вызывает конструкторы в следующем порядке.

1. Конструктор базового класса.

2. Конструкторы всех объектов-членов (т.е. тех элементов класса, которые являются объектами классов). Эти конструкторы вызываются в порядке перечисления объектов в определении класса.

3. Собственный конструктор класса.

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

Таким образом, если код конструктора выполнен, можно быть уверенным в том, что базовый класс и его члены уже инициализированы и их использование допустимо. Аналогично, если выполнен код деструктора, то можно быть уверенным в том, что базовый класс и все объекты-члены еще не уничтожены, и их по-прежнему можно использовать.