Добавлен , опубликован

Основы программирования Корсаров

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

Ликбез

Для понимания всего контекста приведу несколько пояснений.

Блоки кода

Блоки стейтментов (или «составные операторы») — это группа стейтментов, которые обрабатываются как одна инструкция. Блок начинается с символа { и заканчивается символом }, стейтменты находятся внутри. Блоки могут использоваться в любом месте, где разрешено использовать один стейтмент. В конце составного оператора точка с запятой не ставится.
Блоки могут быть вложены друг в друга.
Вы уже видели пример блоков при написании функций, условных операторов и циклов. Так, тело функции является блоком кода. В последующих уроках вам встретится ещё много таких примеров.
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, мы обращаемся к переменной с таким именем, которая создана внутри этого блока, не смотря на то, что существует глобальная переменная с таким же именем.
Именно поэтому в предыдущем примере имя глобальной переменной начиналось с префикса g. Это сокращение от global. Оно нужно для того, чтобы во-первых не путать где глобальные переменные, а где локальные, а во-вторых всегда иметь доступ к глобальной переменной, даже если в функции существует локальная с таким же именем.
В чистом С++ существует способ обращения к переменным из другой области видимости, а в скриптах Корсаров его нет. Поэтому я вам настоятельно рекомендую использовать подобные префиксы, хоть это и кажется не очень удобным.
Вообще говоря, использовать глобальные переменные не рекомендуется, если только они не статичны. Но о том, что такое статические переменные и как это реализовано в Корсарах мы поговорим в отдельном уроке.

`
ОЖИДАНИЕ РЕКЛАМЫ...
38
А что с классами там?
Потому что обычно программа начинается со своего класса Application, который инстанцируется в main и имеет в себе другие сущности и дальше поехал его конструктор и методы, которые инстанцируют остальное и так далее
Ответы (4)
23
ScorpioT1000, а ничего там с классами :D
Пока я не распишу здесь основы, влезать в ковыряние движка не буду.
В двух словах - движок почти полностью на С++. Функция main() и все классы находятся внутри него.
Также в нём есть блок, который обрабатывает эти, так называемые, скрипты.
Выглядит это примерно так: он берёт файл скрипта и ищет там знакомые ключевые слова. Далее по своему внутреннему "словарю" вызывает нужные функции.
Поэтому функционал сильно ограничен, а некоторые, вполне базовые вещи, реализованы через костыли или вообще не реализованы.
Но это всё равно на две головы выше того, что дали нам близзарды в джассе, просто нужно в этом разобраться (чем я сейчас и занимаюсь).
В скриптовой части вместо реальных объектов класса используются текстовые структуры данных.
О них статья будет позже.
38
avuremybe, получается, тут тупо глобалки одни (хотя в самом движке это не глобалки), и даже никаких неймспейсов?
23
ScorpioT1000, исходники движка, пусть и уже сильно доработанные, не так давно выложили в открытый доступ (хотя обещали это сделать ещё лет 14 назад)
Если у тебя появится желание в этом разобраться - милости прошу.
23
ScorpioT1000, да, никакими неймспейсами тут и не пахнет.
Глобалки в чистом виде вообще не используются. И да, на уровне движка это не глобалки))
Никаких static и прочих высших материй тоже нет.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.