За последний год я встречал огромное количество вопросов, связанных с передачей данных между разного рода функциями и переменными в Jass. Про это уже много всего написано, но большая часть из написанного либо не содержит однозначных ответов, либо содержит неверную информацию.
Я решил написать статью, которая предоставит читателю исчерпывающую информацию о передаче и хранении значений и ссылок на языке Jass.
Статья так же содержит примеры на языке cJass для тех, кто уже смело им владеет.
Типы данных
В Jass существует несколько типов данных, группирую я их так:
- Базовые: integer, boolean, real
- Встроенный: string
- Неопределённый: nothing
- Ссылочный: handle
- Производные: agent, unit, player, trigger и все остальные..
Базовые типы
Базовые типы являются аналогами типов на языке Си и обрабатываются так же.
integer - целое 32-битное число (аналог __int32 в Си), принимает значения от -2147483648 до 2147483647.
Константно может быть задан в десятичном (123456), восьмеричном (0361100), шестнадцатеричном виде (0x1E240) и в виде побайтового представления четырёх "чаров" (в Си – char, просто символ ASCII) ('Ab12'). Подробнее об этом написано в статье от ADOLF.
Нулевое значение - просто ноль (0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические целочисленные(+, -, *, /) и набор математических функций (подробнее в common.j).
Стоит заметить, что допускается беспараметровая операция обращения знака, например, "a = -b".
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение:
» Пример на cJass
» Пример на Jass
boolean - логический тип(аналог bool в Си++), хранится как целое 32-битное число (аналог __int32 в Си), принимает два значения.
Константно может быть задан двумя ключевыми словами - true (да) и false (нет).
Нулевое значение – "нет" (false).
Имеет операторы присвоения(=), сравнения(==, !=), логические(and, or, not) и набор дополнительных функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
» Примеры на Jass
real - реальное 32-битное цисло с плавающей точкой (аналог float на Си), принимает значения от -3.4 * 10^38 до 3.4 * 10^38. Выделяется особенностью хранения – точность может быть сверхвысокой в одних и низкой в других диапазонах. Подробнее на Википедии.
Константно может быть задан в виде числа с точкой (1.2345) и целого числа (и тогда он будет преобразован к числу с точкой в процессе выполнения(см. примеры ниже)). Разрешается не ставить число до или после запятой, тогда это будет считаться как ноль ( .12 эквивалентно 0.12, 12. эквивалентно 12.0 ).
Нулевое значение – ноль с точкой (0.0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические(+, -, *, /) и набор математических функций (подробнее в common.j).
Стоит заметить, что допускается беспараметровая операция обращения знака, например, "a = -b".
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
» Примеры на Jass
Базовые типы передаются в функции и служат локальными переменными через стек. По окончанию работы функции, локальные переменные (в т.ч. параметры функции) удаляются.
Встроенный тип string
Тип string является нативным типом в warcraft 3 и представляет из себя ссылку на строку. Ссылка эта – аналог хендла (см. ниже).
Строка в Jass – это последовательность символов в кодировке UTF-8.
На самом деле всё немного сложнее. В памяти располагается так называемый string table – это хеш-таблица, которая содержит в себе все строки, когда-либо созданные в процессе игры. Таблица строк накапливает строки, но не удаляет их, даже если их больше не используют. Неиспользуемые строки удаляются лишь после загрузки сохранённой игры(или конца игры).
Когда мы генерируем какую-либо строку, её хеш-сумма сравнивается на объект дубликата. И если такой строки ещё не было в таблице строк – строка добавляется в эту таблицу, а ссылка типа string возвращается как результат операции. Если же строка уже была создана, то наша временная строка удаляется, а в качестве результата возвращается ссылка на уже существующую строку из таблицы.
Тип string обладает следующими операторами: присвоение(=), сравнение(==, !=) и конкатенация(+).
Операторы не работают с другими типами (например, integer в виде набора чаров), но для этого есть специальные функции конвертации типов в строку и обратно(подробнее в common.j). Так же существуют базовые функции подстроки (SubString), длины строки (StringLength) и другие.
Следует знать, что любые операции создают после себя новую ссылку на строку, поэтому не стоит ими злоупотреблять.
» Пример на cJass
» Пример на Jass
Нулевым значением типа string служит нулл (null) – нулевой указатель.
Константа пустых двойных кавычек ("") не всегда равна этому нулю, по этому не стоит её использовать.
Хранение значений в строке работает по принципу UTF-8: все ASCII-символы занимают один байт, остальные – два байта. Максимальный размер строки – 1023 байта. thx to toadcop
Неопределённый тип nothing
nothing в Jass служит лишь как ключевое слово, которое даёт компилятору понять, что функция не имеет принимаемых или возвращаемых типов. Сам по себе тип nothing объявить нельзя.
Ссылочные типы handle
И так, что же такое этот вездесущий хендл? Очень просто – это число. Обыкновеннное целое число.
Называют его по-разному – идентификатор, ссылка, дескриптор, указатель, индекс, smart pointer и другие.
Хендл в Jass является указателем на объект в памяти. А конкретнее – это адрес ячейки в специальной хеш-таблице хендлов.
Точная структура этой таблицы не известна, но есть основание предполагать, что она устроена следующим образом:
При создании какого-либо объекта (пусть это будет юнит) в памяти динамически создаётся его структура со всеми нужными полями. В таблице хендлов выделяется один элемент под этот объект, который хранит в себе указатель на объект в памяти и дополнительную информацию. А наш хендл – это адрес этого самого элемента.
Когда мы удаляем юнита, удаляется эта структура в памяти (перейдя по нашему хендлу), но сам хендл (и другие хендлы этого юнита) остается неизменным.
Когда мы обнуляем хендл (присваиваем ему null), объект удаляется из таблицы, а наш хендл (как число) принимает нулевое значение. И опять-же, другие хендлы, которые ссылались на наш объект, остаются неизменны.
» Немного мыслей здесь
Именно поэтому, после работы с объектом, его надо не только удалять, но и обнулять все хендлы, ссылающиеся на нашего юнита. Если этого не сделать, они будут "висеть" в таблице хендлов и не только засорять память, но и замедлять скорость доступа к этой таблице.
Исключение – локальные переменные, которые являются параметрами функции. Они удаляются автоматически после выхода из функции.
Тип handle обладает только операторами присвоения(=) и сравнения (==, !=).
Нулевым значением для хендла является нулл(null).
Я думаю, не стоит доказывать, что при обращении к хендлу, значение которого равно нулю, игра вылетает с ошибкой.
То же может касаться и обращений к хендлам объектов, которые были удалены до этого обращения. В большинстве случаев будет ошибка Access Violation, но бывают случаи, когда ваш старый хендл "попадает" в какую-либо "правильную" структуру. И результат непредсказуем.
Return Bug и GetHandleId()
Про это уже очень много написано, например, здесь или здесь.
Но суть одна – получить доступ к хендлам так, чтобы работать с ними было так же удобно, как и с целыми числами.
Зачем это нужно и как их использовать написано в этих статьях:
xgm.guru/project.php?id=100&page=ex_jass "RETURN BUG (RB)"
http://xgm.guru/forum/showthread.php?t=30742
xgm.guru/project.php?id=100&page=w3_special_programming "Стек и "Аттачи""
Производные типы от handle
Все остальные типы, которые являются производными от handle (unit, item, player, code и др. См. common.j) – точно такие же ссылки. Но логически поделены на подтипы, у каждого из которых своя область применения.
Стоит упомянуть, что некоторые вобще не содержат никаких таблиц хендлов, например, тип player. В Starcraft 2 этот тип заменили на int.
|