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

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

Например, мы можем подсчитывать количество фигур, созданных пользователем в программе рисования. Для этого в классе Shape опишем статическую переменную count (Shapes5.mq5).

class Shape
{
private:
   static int count;
   
protected:
   ...
   Shape(int pxint pycolor backstring t) :
      coordinates(pxpy),
      backgroundColor(back),
      type(t)
   {
      ++count;
   }
   
public:
   ...
   static int getCount()
   {
      return count;
   }
};

Она определена в секции private и потому недоступна извне.

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

Будем увеличивать счетчик на 1 в параметрическом конструкторе Shape, а конструктор по умолчанию уберем. Таким образом, каждый экземпляр фигуры любого производного типа окажется учтенным.

Обратите внимание, что статическую переменную необходимо явным образом определить (и опционально проинициализировать) вне блока класса:

static int Shape::count = 0;

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

В данном случае инициализацию нулем можно опустить, потому что, как мы знаем, глобальные и статические переменные по умолчанию получают нулевые значения. Статическими могут быть и массивы.

В определении статической переменной мы видим использование специального оператора выбора контекста '::'. С помощью него формируется полностью квалифицированное имя переменной. Слева от '::' стоит имя класса, к которому относится переменная, а справа — её идентификатор. Очевидно, что полное имя необходимо, потому что внутри разных классов могут быть описаны статические переменные с одним и тем же идентификатором, и нужен способ однозначно обращаться к каждой из них.

Тот же оператор '::' используется для доступа не только к публичным статическим переменным класса, но и методам. В частности, для того чтобы вызвать метод getCount в функции OnStart используем следующий синтаксис — Shape::getCount():

void OnStart()
{
   for(int i = 0i < 10; ++i)
   {
      Shape *shape = addRandomShape();
      shape.draw();
      delete shape;
   }
   
   Print(Shape::getCount()); // 10
}

Поскольку сейчас генерируется заданное количество фигур (10), мы можем убедиться, что счетчик работает правильно.

При наличии объекта класса, можно обратиться к статическому методу или свойству через привычное разыменование (например, shape.getCount()), но такая запись может вводить в заблуждение (поскольку скрывает тот факт, что обращения к объекту на самом деле не происходит).

Отметим, что создание производных классов никак не влияет на статические переменные и методы: они всегда приписаны к тому классу, в котором были определены. Наш счетчик — единый для всех классов фигур, производных от Shape.

Внутри статических методов нельзя использовать this, поскольку они выполняются без привязки к конкретному объекту. Также из статического метода нельзя напрямую, без разыменования какой-либо переменной объектного типа, вызвать обычный метод класса или обращаться к его полю. Например, если вызвать draw из getCount, получим ошибку "доступ к нестатическому члену или функции":

   static int getCount()
   {
      draw(); // error: 'draw' - access to non-static member or function
      return count;
   }

По той же причине статические методы не могут быть виртуальными.

Можно ли, пользуясь статическими переменными, подсчитать не общее количество фигур, а их статистику в разбивке по типам? Да, можно. Эта задача оставлена для самостоятельной проработки. Желающие могут найти один из примеров реализации в скрипте Shapes5stats.mq5.