23-11-2024В статье рассмотрены вопросы, связанные с
передачей данных между разного рода функциями и переменными в Jass. Про
это уже много всего написано, но большая часть из написанного либо не
содержит однозначных ответов, либо содержит неверную информацию.
Я решил написать статью, которая предоставит читателю исчерпывающую
информацию о передаче и хранении значений и ссылок на языке Jass.
_
За последний год я встречал огромное количество вопросов, связанных с
передачей данных между разного рода функциями и переменными в 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').
Нулевое значение - просто ноль (0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические целочисленные(+, -, *, /) и набор математических функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение:
» Пример на cJass
Int i; // неверно //... If( i < 5 ) { //значение неопределено }
int i=0; // верно //... if( i < 5 ) { }
» Пример на Jass
local Integer i // неверно //... If i < 5 then //значение неопределено endif
local integer i=0 // верно //... if i < 5 then endif
boolean - логический тип(аналог bool в Си++), хранится как целое 32-битное число (аналог __int32 в Си), принимает два значения.
Константно может быть задан двумя ключевыми словами - true (да) и false (нет).
Нулевое значение – "нет" (false).
Имеет операторы присвоения(=), сравнения(==, !=), логические(and, or,
not) и набор дополнительных функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
if( (IsUnitFriend(u) || IsUnitDead(u)) && (! IsUnitChuckNorris(u) ) { KickAss(u); }
Здесь происходит проверка – если одна из функций-предикатов
IsUnitFriend и IsUnitDead вернёт true, мы проверяем IsUnitChuckNorris и
если она вернула false, то операция ! в скобках вернёт true и наш if получит ответ true, после чего выполнится KickAss.
» Примеры на Jass
if ( (IsUnitFriend(u) or IsUnitDead(u)) and (not IsUnitChuckNorris(u) ) then call KickAss(u) endif
Здесь происходит проверка – если одна из функций-предикатов
IsUnitFriend и IsUnitDead вернёт true, мы проверяем IsUnitChuckNorris и
если она вернула false, то операция ! в скобках вернёт true и наш if получит ответ true, после чего выполнится KickAss.
real - реальное 32-битное цисло с плавающей точкой (аналог float на Си), принимает значения от -3.4 * 10^38 до 3.4 * 10^38. Выделяется особенностью хранения – точность может быть сверхвысокой в одних и низкой в других диапазонах. Подробнее на Википедии.
Константно может быть задан в виде числа с точкой (1.2345) и целого числа (и тогда он будет преобразован к числу с точкой в процессе выполнения(см. примеры ниже)). Разрешается не ставить число до или после запятой, тогда это будет считаться как ноль ( .12 эквивалентно 0.12, 12. эквивалентно 12.0 ).
Нулевое значение – ноль с точкой (0.0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=,
>=), арифметические(+, -, *, /) и набор математических функций
(подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
float x; float y = 1; float z = 1.1;
Процесс выглядит так:
- В стеке создаётся переменная x с неопределённым значением (инициализация)
- Создаётся переменная y
- Число из константы 1 преобразовывается в число 1.0
- Переменной y присваивается значение 1.0
- Создаётся переменная z
- Переменной z присваивается значение из константы 1.1
» Примеры на Jass
local real x local real y = 1 local real z = 1.1
Процесс выглядит так:
- В стеке создаётся переменная x с неопределённым значением (инициализация)
- Создаётся переменная y
- Число из константы 1 преобразовывается в число 1.0
- Переменной y присваивается значение 1.0
- Создаётся переменная z
- Переменной z присваивается значение из константы 1.1
Базовые типы передаются в функции и служат локальными переменными
через стек. По окончанию работы функции, локальные переменные (в т.ч.
параметры функции) удаляются.
Встроенный тип string
Тип string является нативным типом в warcraft 3 и представляет из себя ссылку на строку. Ссылка эта – аналог хендла (см. ниже).
Строка в Jass – это последовательность символов в кодировке UTF-8.
На самом деле всё немного сложнее. В памяти располагается так
называемый string table – это хеш-таблица, которая содержит в себе все
строки, когда-либо созданные в процессе игры. Таблица строк накапливает
строки, но не удаляет их, даже если их больше не используют. Неиспользуемые строки удаляются лишь после загрузки сохранённой игры(или конца игры).
Когда мы генерируем какую-либо строку, её хеш-сумма сравнивается
на объект дубликата. И если такой строки ещё не было в таблице строк –
строка добавляется в эту таблицу, а ссылка типа string
возвращается как результат операции. Если же строка уже была создана,
то наша временная строка удаляется, а в качестве результата
возвращается ссылка на уже существующую строку из таблицы.
Тип string обладает следующими операторами: присвоение(=), сравнение(==, !=) и конкатенация(+).
Операторы не работают с другими типами (например, integer в виде набора
чаров), но для этого есть специальные функции конвертации типов в
строку и обратно(подробнее в common.j). Так же существуют базовые
функции подстроки (SubString), длины строки (StringLength) и другие.
Следует знать, что любые операции создают после себя новую ссылку на строку, поэтому не стоит ими злоупотреблять.
» Пример на cJass
//плохое решение string f[]; //... string name = "scorpy"; int i=0; while(i<10) { f[i] = name + I2S(i); if(i==9) { f[i] = f[i] + " always here"; } i++; }
//более оптимально string f[]; //... f[0] = "scorpy0"; f[1] = "scorpy1"; f[2] = "scorpy2"; //... f[8] = "scorpy8"; f[9] = "scorpy9 always here";
В первом случае таблица строк будет содержать не только то, что
нам надо, но ещё и строки "0", "1", ... и это только самый простой
случай.
» Пример на Jass
//плохое решение string array f //... local string name = "scorpy" local integer i=0 loop exitwhen (i<10) set f[i] = name + I2S(i) if (i==9) then set f[i] = f[i] + " always here" endif set i=i+1 endloop
//более оптимально string array f //... set f[0] = "scorpy0" set f[1] = "scorpy1" set f[2] = "scorpy2" //... set f[8] = "scorpy8" set f[9] = "scorpy9 always here"
В первом случае таблица строк будет содержать не только то, что
нам надо, но ещё и строки "0", "1", ... и это только самый простой
случай.
Нулевым значением типа 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()
Но суть одна – получить доступ к хендлам так, чтобы работать с ними было так же удобно, как и с целыми числами.
Зачем это нужно и как их использовать написано в этих статьях:
|