Что такое статическая переменная
Готовимся к собеседованию по PHP: ключевое слово «static»
Не секрет, что на собеседованиях любят задавать каверзные вопросы. Не всегда адекватные, не всегда имеющие отношение к реальности, но факт остается фактом — задают. Конечно, вопрос вопросу рознь, и иногда вопрос, на первый взгляд кажущийся вам дурацким, на самом деле направлен на проверку того, насколько хорошо вы знаете язык, на котором пишете.
Попробуем разобрать «по косточкам» один из таких вопросов — что значит слово «static» в PHP и зачем оно применяется?
Ключевое слово static имеет в PHP три различных значения. Разберем их в хронологическом порядке, как они появлялись в языке.
Значение первое — статическая локальная переменная
Однако всё меняется, если мы перед присваиванием поставим ключевое слово static:
Подводные камни статических переменных
Разумеется, как всегда в PHP, не обходится без «подводных камней».
Камень первый — статической переменной присваивать можно только константы или константные выражения. Вот такой код:
с неизбежностью приведет к ошибке парсера. К счастью, начиная с версии 5.6 стало допустимым присвоение не только констант, но и константных выражений (например — «1+2» или «[1, 2, 3]»), то есть таких выражений, которые не зависят от другого кода и могут быть вычислены на этапе компиляции
Камень второй — методы существуют в единственном экземпляре.
Тут всё чуть сложнее. Для понимания сути приведу код:
Такое поведение может быть неожиданным для неподготовленного к нему разработчика и послужить источником ошибок. Нужно заметить, что наследование класса (и метода) приводит к тому, что всё-таки создается новый метод:
Вывод: динамические методы в PHP существуют в контексте классов, а не объектов. И только лишь в рантайме происходит подстановка «$this = текущий_объект»
Значение второе — статические свойства и методы классов
В объектной модели PHP существует возможность задавать свойства и методы не только для объектов — экземпляров класса, но и для класса в целом. Для этого тоже служит ключевое слово static:
Для доступа к таким свойствам и методам используются конструкции с двойным двоеточием («Paamayim Nekudotayim»), такие как ИМЯ_КЛАССА::$имяПеременной и ИМЯ_КЛАССА:: имяМетода().
Само собой разумеется, что у статических свойств и статических методов есть свои особенности и свои «подводные камни», которые нужно знать.
Особенность вторая — static не аксиома!
Обратное не совсем верно:
И кстати, всё написанное выше относится только к методам. Использование статического свойства через «->» невозможно и ведет к фатальной ошибке.
Значение третье, кажущееся самым сложным — позднее статическое связывание
Разработчики языка PHP не остановились на двух значениях ключевого слова «static» и в версии 5.3 добавили еще одну «фичу» языка, которая реализована тем же самым словом! Она называется «позднее статическое связывание» или LSB (Late Static Binding).
Понять суть LSB проще всего на несложных примерах:
Ключевое слово self в PHP всегда значит «имя класса, где это слово написано». В данном случае self заменяется на класс Model, а self::$table — на Model::$table.
Такая языковая возможность называется «ранним статическим связыванием». Почему ранним? Потому что связывание self и конкретного имени класса происходит не в рантайме, а на более ранних этапах — парсинга и компиляции кода. Ну а «статическое» — потому что речь идет о статических свойствах и методах.
Немного изменим наш код:
Теперь вы понимаете, почему PHP ведёт себя в этой ситуации неинтуитивно. self был связан с классом Model тогда, когда о классе User еще ничего не было известно, поэтому и указывает на Model.
Для решения этой дилеммы был придуман механизм связывания «позднего», на этапе рантайма. Работает он очень просто — достаточно вместо слова «self» написать «static» и связь будет установлена с тем классом, который вызывает данный код, а не с тем, где он написан:
Это и есть загадочное «позднее статическое связывание».
Нужно отметить, что для большего удобства в PHP кроме слова «static» есть еще специальная функция get_called_class(), которая сообщит вам — в контексте какого класса в данный момент работает ваш код.
Странные они, статические переменные в PHP
Дисклеймер: данная статья не явит вам какого то откровения и не откроет третий глаз, но позволит разобраться в не очень очевидном вопросе более детально. Мне по крайней мере при ее написании она в этом помогла. Если вы матерый волк в php то можете не читать, опытным человекам думаю не повредит пробежать глазами, освежить так сказать в памяти, остальным будет норм.
Статические переменные, в php это особый вид переменных, которые объявляются при помощи ключевого слова static.
От обычных переменных они отличаются тем что (далее в статье эти пункты будут рассмотрены более подробно):
1. Могут быть присвоены только константы и константные выражения
Это значит что в статическую переменную не может быть присвоен результат работы какой-либо функции или метода, или вообще что-либо что еще не известно на этапе компиляции. То есть вот такое объявление не сработает
а вот так вполне возможно
2. Время жизни статической переменной не ограничено временем жизни области видимости в которой она объявлена
Постараюсь объяснить что тут я имел в виду. Возможно допущу какие-то неточности в терминологии, но суть постараюсь передать как можно точнее. Сравним с обычной переменной. Если внутри функции объявить переменную, то она по умолчанию является локальной переменной, то есть она объявлена в локальной области видимости (области видимости этой функции). В этом случае контекстом данной функции будет локальная область видимости. После того как функция отработала и вернула результат, ее область видимости или ее контекст, со всеми переменными внутри нее будет уничтожена.
Если же мы внутри функции объявляем статическую переменную, то она также объявляется в локальной области видимости, но ее контекстом будет не локальная область видимости, а сама функция.
(Далее самый трудный момент для объяснения, передаю только суть, без подробностей, как объявляются функции в php сколько им выделяется памяти и что в этой памяти лежит). Получается так, при вызове функции, для нее интерпретатором создается локальная область видимости именно в ней объявляются все локальные переменные и ф-ции, и к ней как к своему контексту они и привязаны. Объявляя же переменную в локальной области видимости с помощью static, этой переменной в качестве контекста назначается сама функция, и эта переменная будет существовать до тех пор пока существует сама функция. Это нечто похожее на js, когда функция является объектом, в который можно присваивать произвольные свойства и методы. Тут так же только функция в php является объектом не для php, а для более низкого ЯП.
А вот пример который наглядно покажет что статическая переменная принадлежит функции (или хранится в функции, или ее контекстом является функция уж извините не знаю как это правильно назвать).
Немного поменяем пример, а именно не присвоим, а клонируем
Что можно с этим сделать — это классический пример счетчика вызова функции.
Но в связи с распространением ООП в таком виде статические переменные встречаются редко, так как в основном приходится оперировать классами и методами (о реализации static в них напишу отдельную статью)
3. могут быть определены в скрипте лишь однажды
Это значит что если статическая переменная уже объявлена, и ей присвоено значение, то последующие присвоения не перезапишут уже присвоенное значение, а вернут существующее.
Тут переменная $var в первом вызове функции staticVar в первой ее строке была присвоена, потом перезаписана во второй строке. Но уже в дальнейших вызовах ни в первой ни во второй строке она не была переприсвоена, а вернула то что уже было в предыдущем вызове
Еще страннее при первом вызове staticVar в первой строке она была присвоена, потом во второй строке переприсвоена (но неуспешно), затем с ней было произведено действие сложения, и уже после этого при попытки ее переприсвоить даже в рамках первого вызова функции она вернула уже лежащее в ней значение.
То есть получается в практически одинаковый методах, разное поведение. Причем исходя из описания того как должны себя вести статические переменные то правильный результат получается именно в staticVarRight. В staticVarWrong получается (исходя из поведения функции) что во второй строке функции переменная была переопределена.
Меня это довольно сильно позабавило.
4. не уничтожаются до конца выполнения скрипта
Не вижу особого смысла объяснять этот пункт, тем более что из примеров все видно. Пока работает скрипт и пока существует функция для которой объявлена статическая переменная, эта переменная существует.
Как планируется — это первая статья по static, впереди еще ООП, статические поля, методы.
Ну конечно если это хоть кому-то будет интересно и жестко не заминусуется.
Статические переменные и методы
— Новая интересная тема. Хочу рассказать тебе о статических переменных и методах.
— О, я уже слышал про статические переменные. И про статические методы, кажется. Но хотелось бы больше подробностей.
— Когда мы описываем переменные в классе, мы указываем, будут ли эти переменные созданы всего один раз или же нужно создавать их копии для каждого объекта. По умолчанию создаётся новая копия переменной для каждого объекта. Вот как это выглядит:
— Методы класса тоже делятся на две категории. Обычные методы вызываются у объекта и имеют доступ к данным этого объекта. Статические методы не имеют такого доступа – у них просто нет ссылки на объект, они способны обращаться либо к статическим переменным этого класса либо к другим статическим методам.
— Статические методы не могут обращаться к нестатическим методам или нестатическим переменным!
— Каждая обычная переменная класса находится внутри объекта. Обратиться к ней можно только имея ссылку на этот объект. В статический метод такая ссылка не передается.
— А в обычные методы передается?
— В статический метод вместо ссылки на объект передается null. Поэтому он не может обращаться к нестатическим переменным и методам – у него банально нет ссылки на объект, к которому они привязаны.
— Так работают обычные нестатические методы:
— А вот как работают статические методы:
— Переменная или метод являются статическими, если перед ними стоит ключевое слово static.
— А зачем такие методы нужны, если они так сильно ограничены?
— У такого подхода тоже есть свои преимущества.
— Во-первых, для того, чтобы обратиться к статическим методам и переменным не надо передавать никакую ссылку на объект.
— Во-вторых, иногда бывает нужно, чтобы переменная была в единственном экземпляре. Как, например, переменная System.out (статическая переменная out класса System).
— И в третьих, иногда нужно вызвать метод, еще до того, как будет возможность создавать какие-то объекты
— А почему, по-твоему, метод main объявлен статическим? Чтобы его можно было вызвать сразу после загрузки класса в память. Еще до того, когда можно будет создавать какие-то объекты.
Статические переменные
1. Статические переменные
Когда класс загружается в память, для него сразу создается статический объект класса. Этот объект хранит статические переменные класса (статические поля класса). Статический объект класса существует, даже если не был создан ни один обычный объект класса.
Когда мы описываем переменные в классе, мы указываем, будут ли эти переменные созданы всего один раз или же нужно создавать их копии для каждого объекта. По умолчанию создаётся новая копия переменной для каждого объекта.
Статическая (static) же переменная привязана к статическому объекту класса и всегда существует в единственном экземпляре.
Если статической переменной не присвоить стартовое значение, она инициализируется значением по умолчанию:
Тип | Значение по умолчанию |
---|---|
(то же самое, что и ) | |
и любые классы |
Обращаться к статической переменной в ее классе можно просто по имени. Если обращение идет из другого класса, то перед именем статической переменной нужно писать имя класса.
2. Отличие статических и нестатических переменных
Чем же отличаются обычные и статические переменные?
Обычные переменные класса привязаны к объектам своего класса (экземплярам класса), статические переменные — к статическому объекту класса.
Если экземпляров класса несколько, в каждом из них существует своя копия нестатических (обычных) переменных класса. Статические переменные класса всегда находятся внутри статического объекта класса и существуют только в одном экземпляре.
Обращаться к обычным переменным класса (полям класса) можно только имея ссылку на объект класса. Ну или в методах внутри этого же класса.
Обращение к полю класса с использованием ссылки на объект класса |
---|
Обращаться к статическим переменным можно откуда угодно (с учетом модификаторов видимости): из обычных методов, из статических методов того же класса, из методов других классов и т.п.
Обращение к статическому полю класса не используя ссылку на объект класса |
---|
Устройство в памяти:
Допустим, у нас есть класс Person с 4 полями: два статических, а два — нет.
Сразу после загрузки класса
После создания первого объекта
Обратите внимание, что хоть у объектов по две переменные, это разные переменные: у обычного объекта — обычные, у статического — статические.
Нужно больше объектов
Обратите внимание: у каждого объекта есть собственная переменная age и name.
6.10 – Статические локальные переменные
Термин статический ( static ) – один из самых запутанных терминов в языке C++, в значительной степени потому, что static в разных контекстах имеет разные значения.
В предыдущих уроках мы рассмотрели, что глобальные переменные имеют статическую продолжительность, что означает, что они создаются при запуске программы и уничтожаются при ее завершении.
Мы также обсудили, как ключевое слово static обеспечивает внутреннее связывание глобального идентификатора, что означает, что идентификатор может использоваться только в том файле, в котором он определен.
В этом уроке мы рассмотрим использование ключевого слова static в применении к локальной переменной.
Статические локальные переменные
В уроке «2.4 – Локальная область видимости в C++» видимости вы узнали, что локальные переменные по умолчанию имеют автоматическую продолжительность, что означает, что они создаются в точке определения и уничтожаются при выходе из блока.
Использование ключевого слова static для локальной переменной изменяет ее продолжительность с автоматической на статическую. Это означает, что теперь переменная создается при запуске программы и уничтожается при ее завершении (как глобальная переменная). В результате статическая переменная сохранит свое значение даже после выхода за пределы области видимости!
Показать разницу между автоматической и статической продолжительностями переменных проще всего на примере.
Автоматическая продолжительность (по умолчанию):
Теперь рассмотрим статическую версию этой программы. Единственная разница между этой и приведенной выше программами состоит в том, что мы с помощью ключевого слова static изменили продолжительность локальной переменной с автоматической на статическую.
Статическая продолжительность (с использованием ключевого слова static ):
В этой программе, поскольку s_value была объявлена как статическая, s_value создается и инициализируется один раз (при запуске программы). Если бы мы для инициализации s_value не использовали константное выражение, при запуске программы она была бы инициализирована нулем, а затем, при первом обнаружении определения переменной, инициализировалась бы нашим предоставленным значением инициализации (но при последующих вызовах она не повторно инициализируется).
Точно так же, как мы используем » g_ » для префикса глобальных переменных, для префикса статических (со статической продолжительностью) локальных переменных часто используется » s_ «.
Одно из наиболее распространенных применений локальных переменных статической продолжительности – генераторы уникальных идентификаторов. Представьте себе программу, в которой есть много похожих объектов (например, игра, в которой на вас нападает множество зомби, или симуляция, в которой вы показываете много треугольников). Если вы заметили дефект, будет практически невозможно определить, с каким объектом возникли проблемы. Однако если каждому объекту при создании присваивается уникальный идентификатор, тогда при дальнейшей отладке объекты будет проще различать.
Сгенерировать уникальный идентификационный номер очень просто с помощью локальной переменной статической продолжительности:
При первом вызове этой функции она возвращает 0. Во второй раз она возвращает 1. Каждый раз, когда она вызывается, она возвращает число на единицу больше, чем при предыдущем вызове. Вы можете присвоить эти номера своим объектам как уникальные идентификаторы. Поскольку s_itemID является локальной переменной, другие функции не могут «подделать» ее.
Статические переменные обладают некоторыми преимуществами глобальных переменных (они не уничтожаются до конца работы программы), ограничивая при этом свою видимость до области видимости блока. Это делает их безопасными для использования, даже если вы регулярно меняете их значения.
Чрезмерное использование статических локальных переменных
Рассмотрим следующий код
Пример вывода программы:
Предположим, мы хотим добавить в калькулятор вычитание, чтобы вывод программы выглядел следующим образом
Мы могли бы попытаться использовать getInteger() для чтения следующих двух целых чисел, как мы это делали для сложения.
Но это не сработало, на выходе мы получаем:
При запросе первого числа для вычитания мы получаем « Enter another integer » (введите другое целое число) вместо « Enter an integer » (введите целое число).
getInteger() нельзя использовать повторно, потому что у нее есть внутреннее состояние (статическая локальная переменная s_isFirstCall ), которое не может быть сброшено извне. s_isFirstCall – это не та переменная, которая должна быть уникальной во всей программе. Хотя наша программа отлично работала, когда мы написали ее впервые, статическая локальная переменная не позволяет нам повторно использовать функцию в дальнейшем.
Лучший способ реализовать getInteger – передать s_isFirstCall в качестве параметра. Это позволяет вызывающей функции выбрать, какая подсказка будет напечатана.
Статические локальные переменные следует использовать только в том случае, если во всей вашей программе и в обозримом будущем вашей программы переменная уникальна и не имеет смысла эту переменную сбрасывать.
Лучшая практика
Избегайте использования статических локальных переменных, если только переменную не нужно сбрасывать. Статические локальные переменные уменьшают возможность повторного использования и затрудняют понимание функций.
Небольшой тест
Вопрос 1
Как влияет использование ключевого слова static на глобальную переменную? Как оно влияет на локальную переменную?
При применении к глобальной переменной ключевое слово static определяет глобальную переменную как имеющую внутреннее связывание, что означает, что переменная не может быть экспортирована в другие файлы.
При применении к локальной переменной ключевое слово static определяет локальную переменную как имеющую статическую продолжительность, что означает, что переменная будет создана только один раз и не будет уничтожена до конца выполнения программы.