На предыдущем уроке мы узнали, что статические переменные-члены — это члены, которые принадлежат классу, а не его объектам.
Статические методы
Если статические переменные-члены являются открытыми, то мы можем получить к ним доступ напрямую через имя класса и оператор разрешения области видимости. Но что, если статические переменные-члены являются закрытыми? Рассмотрим следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Anything { private: static int s_value; }; int Anything::s_value = 3; // определение статического члена, несмотря на то, что он является private int main() { // Как получить доступ к Anything::s_value здесь, если s_value является private? } |
В этом случае мы не можем напрямую получить доступ к Anything::s_value
из функции main(), так как этот член является private. Обычно, доступ к закрытым членам класса осуществляется через public-методы. Хотя мы могли бы создать обычный метод для получения доступа к s_value
, но нам тогда пришлось бы создавать объект этого класса для использования метода! Есть вариант получше: мы можем сделать метод статическим.
Подобно статическим переменным-членам, статические методы не привязаны к какому-либо одному объекту класса. Вот вышеприведенный пример, но уже со статическим методом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Anything { private: static int s_value; public: static int getValue() { return s_value; } // статический метод }; int Anything::s_value = 3; // определение статической переменной-члена класса int main() { std::cout << Anything::getValue() << '\n'; } |
Поскольку статические методы не привязаны к определенному объекту, то их можно вызывать напрямую через имя класса и оператор разрешения области видимости, а также через объекты класса (но это не рекомендуется).
Статические методы не имеют указателя *this
У статических методов есть две интересные особенности.
Во-первых, поскольку статические методы не привязаны к объекту, то они не имеют скрытого указателя *this! Здесь есть смысл, так как указатель *this всегда указывает на объект, с которым работает метод. Статические методы могут не работать через объект, поэтому и указатель *this не нужен.
Во-вторых, статические методы могут напрямую обращаться к другим статическим членам (переменным или функциям), но не могут напрямую обращаться к нестатическим членам. Это связано с тем, что нестатические члены принадлежат объекту класса, а статические методы — нет!
Еще один пример
Статические методы можно определять вне тела класса. Это работает так же, как и с обычными методами. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> class IDGenerator { private: static int s_nextID; // объявление статической переменной-члена public: static int getNextID(); // объявление статического метода }; // Определение статической переменной-члена находится вне тела класса. Обратите внимание, мы не используем здесь ключевое слово static. // Начинаем генерировать ID с 1 int IDGenerator::s_nextID = 1; // Определение статического метода находится вне тела класса. Обратите внимание, мы не используем здесь ключевое слово static int IDGenerator::getNextID() { return s_nextID++; } int main() { for (int count=0; count < 4; ++count) std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n'; return 0; } |
Результат выполнения программы:
The next ID is: 1
The next ID is: 2
The next ID is: 3
The next ID is: 4
Обратите внимание, поскольку все переменные и функции этого класса являются статическими, то нам не нужно создавать объект этого класса для работы с ним! Статическая переменная-член используется для хранения значения следующего идентификатора, который должен быть ей присвоен, а статический метод — для возврата идентификатора и его увеличения.
Предупреждение о классах со всеми статическими членами
Будьте осторожны при написании классов со всеми статическими членами. Хотя такие «чисто статические классы» могут быть полезны, но они также имеют свои недостатки.
Во-первых, поскольку все статические члены создаются только один раз, то несколько копий «чисто статического класса» быть не может (без клонирования класса и его дальнейшего переименования). Например, если нам нужны два независимых объекта класса IDGenerator, то это будет невозможно через «чисто статический» класс.
Во-вторых, из урока о глобальных переменных мы знаем, что глобальные переменные опасны, поскольку любая часть кода может изменить их значения и, в конечном итоге, изменит другие фрагменты, казалось бы, не связанного с этими переменными кода (детально см. здесь). То же самое справедливо и для «чисто статических» классов. Поскольку все члены принадлежат классу (а не его объектам), а классы имеют глобальную область видимости, то в «чисто статическом классе» мы объявляем глобальные функции и переменные со всеми минусами, которые они имеют.
C++ не поддерживает статические конструкторы
Если вы можете инициализировать обычную переменную-член через конструктор, то по логике вещей вы должны иметь возможность инициализировать статические переменные-члены через статический конструктор. И, хотя некоторые современные языки программирования действительно поддерживают использование статических конструкторов именно для этой цели, язык C++, к сожалению, не является одним из таковых.
Если ваша статическая переменная может быть инициализирована напрямую, то конструктор не нужен: вы можете определить статическую переменную-член, даже если она является private. Мы делали это в вышеприведенном примере с s_nextID
. Вот еще один пример:
1 2 3 4 5 6 7 |
class Something { public: static std::vector<char> s_mychars; }; std::vector<char> Something::s_mychars = { 'o', 'a', 'u', 'i', 'e' }; // определяем статическую переменную-член |
Если для инициализации вашей статической переменной-члена требуется выполнить код (например, цикл), то есть несколько разных способов это сделать. Следующий способ является лучшим из них:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <vector> class Something { private: static std::vector<char> s_mychars; public: class _nested // определяем вложенный класс с именем _nested { public: _nested() // конструктор _nested() инициализирует нашу статическую переменную-член { s_mychars.push_back('o'); s_mychars.push_back('a'); s_mychars.push_back('u'); s_mychars.push_back('i'); s_mychars.push_back('e'); } }; // Статический метод для вывода s_mychars static void getSomething() { for (auto const &element : s_mychars) std::cout << element << ' '; } private: static _nested s_initializer; // используем статический объект класса _nested для гарантии того, что конструктор _nested() выполнится }; std::vector<char> Something::s_mychars; // определяем нашу статическую переменную-член Something::_nested Something::s_initializer; // определяем наш статический s_initializer, который вызовет конструктор _nested() для инициализации s_mychars int main() { Something::getSomething(); return 0; } |
Результат выполнения программы:
o a u i e
При определении статического члена s_initializer
вызовется конструктор по умолчанию _nested() (так как s_initializer
является объектом класса _nested). Мы можем использовать этот конструктор для инициализации любых статических переменных-членов класса Something. Самое крутое здесь — это то, что весь код инициализации скрыт внутри исходного класса со статическим членом.
Заключение
Статические методы могут использоваться для работы со статическими переменными-членами класса. Для работы с ними не требуется создавать объекты класса.
Классы могут быть «чисто статические» (со всеми статическими переменными-членами и статическими методами). Однако, такие классы, по сути, эквивалентны объявлению функций и переменных в глобальной области видимости, и этого следует избегать, если у вас нет на это веских причин.
Нужно чаще практические задания давать. С использованием знаний из предыдущих нескольких уроков. Успеваю забыть некоторые моменты в таких длинных главах. С массивами-указателями так же например.
Зачем писать вне класса
Ведь внутри класса описано что это вектор типа char.
Почему недостаточно просто
?
Когда мы объявляем статическую переменную-член внутри тела класса, то мы сообщаем компилятору о существовании статической переменной-члене, но не о её определении (аналогией является предварительное объявление). https://ravesli.com/urok-124-staticheskie-peremennye-chleny-klassa/#toc-2
Аналогично предварительному объявлению:
Про push_back следовало внести ясность, что это команда добавления нового элемента в конец вектора с последующем увеличение длины вектора на 1. Думаю не я один растерялся поначалу, пока не загуглил.
В предыдущих уроках векторы уже разбирались же. Думаю, автор предполагает, что вы изучаете все уроки последовательно
Что это за конструкция и как она работает?
В строке выше
нам нужно было добраться до статической переменной s_mychars снаружи. Это делается через имя класса Something и оператор разрешения области видимости ::
Перед этим еще нужно указать тип переменной.
Теперь же, нам нужно добраться до переменной s_initializer класса Something.
Это переменная имеет тип _nested.
_nested является вложенным классом, внутри класса Something.
Поэтому чтобы добраться до типа переменной s_initializer снаружи, его тоже нужно вызывать через Something::_nested
После типа пишем саму переменную — Something::s_initializer
Зачем эта конструкция в последнем примере:
Это ж тоже самое, что s_mychars = { 'o', 'a', 'u', 'i', 'e' }?
Ещё не сказано, что статическая функция-член не может иметь квалификатор const, причина такая же как и с *this.
Кстати, а в чем заключается проблема добавления статических конструктов в новых версиях с++?
к последнему коду жизнь меня не готовила) минус мозг
Как наследуются статические методы?
До наследования ещё не дошли)
Отвечаю скорее, для тех, у кого возникнет такой же вопрос при чтении данного урока.
Как и говорилось в этом уроке, статические члены принадлежат самому классу, и похожи на глобальные переменные, но доступны только через класс. Соответственно, при наследовании не создаётся ещё одна глобальная переменная, один статический член на класс и на всех потомков.