Область видимости — это часть программы, в которой мы можем обратиться к переменной, функции или объекту. Этой частью может быть функция, блок или вся программа в целом — то есть мы всегда находимся как минимум в одной области видимости.
Ликбез
Для понимания всего контекста приведу несколько пояснений.
Блоки кода
Блоки стейтментов (или «составные операторы») — это группа стейтментов, которые обрабатываются как одна инструкция. Блок начинается с символа { и заканчивается символом }, стейтменты находятся внутри. Блоки могут использоваться в любом месте, где разрешено использовать один стейтмент. В конце составного оператора точка с запятой не ставится.
Блоки могут быть вложены друг в друга.
Блоки могут быть вложены друг в друга.
Вы уже видели пример блоков при написании функций, условных операторов и циклов. Так, тело функции является блоком кода. В последующих уроках вам встретится ещё много таких примеров.
int division(int x, int y)
{ // начало внешнего блока
if (y == 0)
{ // начало вложенного блока
return 0;
} // конец вложенного блока
return (x / y);
} // конец внешнего блока
Конфликт имён
Конфликт имен возникает, когда два одинаковых идентификатора находятся в одной области видимости, и компилятор не может понять, какой из этих двух следует использовать в конкретной ситуации. Как только программы увеличиваются в объемах, количество идентификаторов также увеличивается, следовательно, увеличивается и вероятность возникновения конфликтов имен.
Например, мы объявили две переменные с одинаковыми именами. Как компьютер должен понять, к какой из них мы обращаемся?
Локальная область видимости
Область видимости переменной определяет, кто может видеть и использовать переменную во время её существования. Аргументы функции и переменные, которые объявлены внутри неё, имеют локальную область видимости. Другими словами, они существуют и используются только внутри функции, в которой объявлены. Локальная переменная создаётся в точке объявления и уничтожается, когда выходит из своей области видимости (в данном случае - когда функция завершается).
Рассмотрим следующую программу:
int add(int a, int b) // здесь создаются переменные a и b
{
// a и b можно видеть/использовать только внутри этой функции
return a + b;
} // здесь a и b выходят из области видимости и уничтожаются
int someFunc()
{
int x = 7; // здесь создается и инициализируется переменная x
int y = 8; // здесь создается и инициализируется переменная y
// x и y можно использовать только внутри функции someFunc()
add(x, y); // вызов функции add() с a = x и b = y
return 0;
} // здесь x и y выходят из области видимости и уничтожаются
Переменные a и b, выступающие аргументами функции add(), создаются при вызове этой функции, используются только внутри неё и уничтожаются по завершении выполнения этой функции.
Переменные x и y функции someFunc() можно использовать только внутри someFunc() и они также уничтожаются по завершении выполнения функции someFunc().
Конфликт имён
Локальная область видимости предотвращает возникновение конфликтов имен.
Немного изменим нашу программу:
int add(int a, int b) // здесь создаются переменные a и b функции add()
{
return a + b;
} // здесь a и b функции add() выходят из области видимости и уничтожаются
int someFunc()
{
int a = 7; // здесь создается переменная a функции someFunc()
int b = 8; // здесь создается переменная b функции someFunc()
add(a, b); // значения переменных a и b функции someFunc() копируются в переменные a и b функции add()
return 0;
} // здесь a и b функции someFunc() выходят из области видимости и уничтожаются
Мы изменили имена переменных x и y функции someFunc() на a и b. Программа по-прежнему работает корректно, несмотря на то, что функция add() также имеет переменные a и b. Почему это не вызывает конфликта имен? Дело в том, что a и b, принадлежащие функции someFunc(), являются локальными переменными - область их видимости ограничена блоком кода этой функции. Ф-ция add() при этом не может их видеть, точно так же, как функция someFunc() не может видеть переменные a и b, принадлежащие функции add(). Ни add(), ни someFunc() не знают, что они имеют переменные с одинаковыми именами!
Это значительно снижает возможность возникновения конфликта имен. Любая функция не должна знать или заботиться о том, какие переменные находятся в другой функции. Это также предотвращает возникновение ситуаций, когда одни функции могут непреднамеренно (или намеренно) изменять значения переменных других функций.
Межблочное поведение
Описанное выше порождает некоторые правила поведения переменных, которые объявлены в одном блоке, а вызываются в другом.
Общая идея состоит в том, что вложенный блок наследует все переменные, которые может видеть его родитель. Т.е. все переменные, которые доступны во внешнем блоке, будут доступны и во вложенном в него (мы уже это видели, когда разбирали условные операторы).
А вот переменные, объявленные во вложенном блоке, считаются локальными для этого конкретного блока и его родитель их видеть не может. Но! ОСОБЕННОСТЬ скриптовой части такова, что движок считывает все локальные переменные в пределах функции, не разбивая при этом области видимости на вложенные блоки. Таким образом, приведённая ниже конструкция хоть и выглядит бредово, но вполне валидна:
void myFunc ()
{
int i = 1;
while (i < 2)
{
int innerVar = 12; // Объявляем переменную во вложенном блоке
}
trace("innerVar: " + innerVar); // Результат "innerVar: 12"
}
Исходя из этого, можно понять, что конфликт имён между разными блоками работает только на уровне функций. Попытка объявить переменную с таким же именем, как уже существует во внешнем блоке, приведёт к аварийному завершению функции, в которой находится этот код.
void myFunc ()
{
int i = 1;
int innerVar = 8; // Объявляем переменную во внешнем блоке
while (i < 2)
{
int innerVar = 12; // Объявляем во вложенном блоке переменную с таким же именем
}
trace("innerVar: " + innerVar); // А результата мы не увидим, потому что выполнение функции до этой строки не дойдёт
}
Глобальная область видимости
Глобальными называются переменные, которые объявлены вне блока кода. Они имеют статическую продолжительность жизни, т.е. создаются при запуске программы и уничтожаются при её завершении. Глобальные переменные имеют глобальную область видимости (или «файловую область видимости»), т.е. их можно использовать в любом месте файла, после их объявления.
Глобальные переменные принято объявлять в верхней части кода. Ниже директив #include, но выше остального кода в файле:
#include "DIALOGS\Alan Milds_dialog.h"
gMyGlobalVariable; // Объявляем глобальную переменную
void ProcessDialogEvent()
{
gMyGlobalVariable = 12; // Затем можем обращаться к ней в любом месте файла
}
На глобальные переменные действует тот же свод правил. Но здесь вспоминаем, что на уровне блоков кода функций Storm Engine всё таки разделяет области видимости. Поэтому мы вполне можем создать локальную переменную с таким же именем, какое имеет глобальная переменная:
int myVar = 8; // Создаём глобальную переменную
void func_one ()
{
trace(myVar); // Результат: "8"
}
void func_two ()
{
int myVar = 12; // Создаём локальную переменную с таким же именем
trace(myVar); // Результат: "12"
}
Из приведённого примера видно, что локальная область видимости конкретного блока имеет приоритет над тем, что видно снаружи.
То есть обращаясь к переменной myVar, мы обращаемся к переменной с таким именем, которая создана внутри этого блока, не смотря на то, что существует глобальная переменная с таким же именем.
То есть обращаясь к переменной myVar, мы обращаемся к переменной с таким именем, которая создана внутри этого блока, не смотря на то, что существует глобальная переменная с таким же именем.
Именно поэтому в предыдущем примере имя глобальной переменной начиналось с префикса g. Это сокращение от global. Оно нужно для того, чтобы во-первых не путать где глобальные переменные, а где локальные, а во-вторых всегда иметь доступ к глобальной переменной, даже если в функции существует локальная с таким же именем.
В чистом С++ существует способ обращения к переменным из другой области видимости, а в скриптах Корсаров его нет. Поэтому я вам настоятельно рекомендую использовать подобные префиксы, хоть это и кажется не очень удобным.
В чистом С++ существует способ обращения к переменным из другой области видимости, а в скриптах Корсаров его нет. Поэтому я вам настоятельно рекомендую использовать подобные префиксы, хоть это и кажется не очень удобным.
Вообще говоря, использовать глобальные переменные не рекомендуется, если только они не статичны. Но о том, что такое статические переменные и как это реализовано в Корсарах мы поговорим в отдельном уроке.
Потому что обычно программа начинается со своего класса Application, который инстанцируется в main и имеет в себе другие сущности и дальше поехал его конструктор и методы, которые инстанцируют остальное и так далее
Пока я не распишу здесь основы, влезать в ковыряние движка не буду.
Также в нём есть блок, который обрабатывает эти, так называемые, скрипты.
Выглядит это примерно так: он берёт файл скрипта и ищет там знакомые ключевые слова. Далее по своему внутреннему "словарю" вызывает нужные функции.
Поэтому функционал сильно ограничен, а некоторые, вполне базовые вещи, реализованы через костыли или вообще не реализованы.
Но это всё равно на две головы выше того, что дали нам близзарды в джассе, просто нужно в этом разобраться (чем я сейчас и занимаюсь).
О них статья будет позже.
Ред. avuremybe