Как создать, присвоить, изменить и т.д. переменную в программе, мы уже подробно обсуждали, начиная с того, как она хранится в памяти.
В какой-то момент мы сталкиваемся с понятием глобальных и локальных переменных, и это начинает слегка путать.
Сначала посмотрим, что это значит, а потом разберёмся, зачем.
Глобальные переменные это те, которые существуют глобально, то есть доступны из любого места программы, и не исчезают до самого конца работы программы. Глобальной переменной становится любая переменная, которую мы объявили в главном теле программы (то есть не внутри функции):
var a = 5;
function test1() { print a; }
function test2() { print a; }
В вышеуказанном коде и функция test1(), и функция test2() видят и могут использовать переменную a, объявленную вне их. Её видимость глобальна.
В свою очередь, локальные переменные это те, которые объявляются внутри функции: они появляются тогда, когда функция начинает работать, видны только внутри этой функции, и исчезают после выхода из функции.
function test1() { var a = 5; }
function test2() { print a; }
Здесь переменная a объявлена внутри функции test1(), а функция test2() пытается к ней получить доступ. Но ничего не получится: переменная a, объявленная в функции test1(), попросту не видна нигде, кроме как в этой функции. Кроме того, сама запись var a = 5, хоть мы её и видим сейчас своими глазами внутри функции, не создаёт никакой переменной до тех пор, пока функция не начнёт реально работать. А после выхода из функциии эта переменная исчезнет.
В общем, тут вроде всё логично. В замкнутом внутреннем мире мы можем иметь свои переменные, но также иметь доступ к переменным внешнего, глобального мира. А из внешнего мира не видно то, что что происходит во внутреннем.
Но остаются вопросы. Зачем вообще придумали локальные переменные? Почему нельзя пользоваться только глобальными переменными?
Как и всё в программировании, подобное разделение появилось не просто так.
Давайте для начала представим, что будет, если пользоваться только глобальными переменными.
У нас могут быть переменные, в которых хранится текущее состояние программы, но также могут быть переменные, которые нужны только для промежуточных расчетов. Например,
d = b * b - 4 * a * c
Это формула вычисления дискриминанта квадратного уравнения, и переменные a, b, c, d нужны лишь для этой формулы. Но объявив их глобальными, вы помещаете их в память на всю жизнь программы и делаете видными всем.
Далее, для какого-то другого вычисления вам опять будут нужны временные переменные, и у вас тогда будет выбор: или создать ещё глобальных переменных с новыми именами, или попытаться повторно использовать те, что уже есть.
Оба решения будут плохими.
Если создавать новые глобальные переменные для каждого случая, вы получите огромный набор переменных, который займёт много памяти, и кроме того придётся помучиться, чтобы их всех назвать по-разному и запомнить эти имена.
Если использовать имеющиеся переменные повторно, то во-первых их названия потеряют всякий смысл, и код станет очень плохо читаемым. Во-вторых, в коде очень легко будет допустить ошибку, использовав переменную, которая уже используется.
Самый простой пример: у меня есть глобальная переменная i, и я делаю с ней цикл:
for (i = 0; i < 20; i++) test();
Внутри цикла я вызываю функцию test(), и вдруг что-то идёт не так. Оказывается, внутри функции я тоже использую глобальную переменную i, но забыл об этом.
Куда как проще для локальных задач использовать локальные переменные – тогда мы точно будем знать, что они не пересекаются ни с какими другими переменными, и не будут занимать память после того, как стали не нужны. Посмотрим на такой пример:
var a = 5;
function test1() { var a = 10; }
function test2() { var a = 20; }
Здесь мы видим глобальную переменную a, которая равна 5, и две функции, test1() и test2(). Каждая функция объявляет внутри себя локальную переменную a. Несмотря на то, что мы видим три переменные с одинаковым именем, ничего плохого не происходит. Локальная переменная a и глобальная переменная a имеют одно и то же имя. Но для функции актуальна только локальная переменная a. Если бы мы назвали её b, то функция видела бы и локальную переменную b, и глобальную переменную a. Но так как мы назвали локальную переменную a, то функция больше не видит глобальную переменную a – локальная a "загородила собой" глобальную.
Такая же переменная a объявлена внутри функции test2() и тоже не имеет никакой связи ни с глобальной переменной a, ни с переменной а в функции test1(). Все они изолированы друг от друга.
Это значит, что например, делая цикл внутри функции:
function test() { for (var i = 0; i < 10; i++) ... }
вы можете не переживать насчёт того, что переменная i используется где-то ещё (в главном теле программы или внутри другой функции). Это исключительно ваша, локальная переменная i.
Как создаются и уничтожаются локальные переменные?
В следующих материалах я напишу более подробно про распределение памяти в программе, а пока вкратце: локальные переменные создаются в стеке. Так как стек используется и для вызова функций, и для передачи параметров в функции, то его же легко использовать для того, чтобы хранить несколько локальных переменных. После выхода из функции стек восстанавливается до такого состояния, которое было до вызова функции, а это значит, что всё, что в него было помещено, пока функция работала – теряется.
Особенности работы с глобальными переменными
Практика использования глобальных переменных внутри функций считается ущербной. Как правило, хорошо написанная функция должна работать только с локальными переменными. Это уменьшает количество зависимостей в коде. Иначе говоря, функция не должна знать, где и зачем существует какая-то глобальная переменная, и как она называется. Функция должна быть самодостаточным куском кода, который можно перенести куда угодно. Глобальные переменные можно передавать в функцию в качестве параметров. В этом случае функции будет уже всё равно, с чем она работает.
Но есть и минусы
Каждый параметр, переданный в функцию – это лишняя операция со стеком. Каждая локальная переменная, созданная в функции – это также лишняя операция со стеком. Ничего бесплатного не бывает. В большинстве случаев можно этим совершенно не заморачиваться. Это вряд ли значительно повлияет на скорость выполнения программы.
Но когда абсолютно необходимо выжать из процессора всё, стоит подумать о размене: удобство, читаемость и переносимость кода или дополнительные несколько тактов процессора. И тогда можно попробовать использовать в функции глобальные переменные.
Языки без объявления переменных
В некоторых языках (C, Java, JavaScript) мы явно указываем, когда хотим создать переменную:
int a = 5;
или
var a = 5;
Указаниями к созданию являются ключевые слова int или var. Поэтому, когда мы создаём локальную переменную в функции, язык видит, что она именно создаётся, что это новая переменная, и он не спутает её с глобальной:
В других языках (PHP, Python) переменная создаётся просто через присваивание:
$a = 5;
или
a = 5
Значит, если у нас есть глобальная переменная а и мы написали в функции a = 5, то язык не сможет определить, чего мы хотим. Мы хотим присвоить глобальной переменной a значение 5, или мы хотим создать локальную переменную a и присвоить ей 5?
В этом случае, если мы хотим работать именно с глобальной переменной, нужно явно указать, что она глобальная (PHP):
и Python:
В Питоне всё несколько более заморочено. Вы можете читать значение глобальных переменных из функции, не используя слово global. Но чтобы изменить значение глобальной переменной, нужно уже использовать global. Кроме того, если глобальная переменная с таким именем не существует, они будет создана прямо из функции, что вообще-то очень нехорошо. В общем, новичкам, изучающим Питон, не позавидуешь :)