Передача и хранение данных в варкрафте | |
Типы данных
В 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). Стоит заметить, что
допускается беспараметровая операция обращения знака, например, "a =
-b". Инициализируется неопределённым значением, поэтому стоит всегда явно
задавать начальное значение:
» Пример на
cJass Int i; // неверно //...
< 5 ) { //значение неопределено
If( i
}
int i=0; // верно //...
< 5 ) {
if( i
}
» Пример на
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,
то операция not в скобках вернёт 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). Стоит заметить, что
допускается беспараметровая операция обращения знака, например, "a =
-b". Инициализируется неопределённым значением, поэтому стоит всегда явно
задавать начальное значение.
» Примеры на
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, но бывают случаи, когда ваш старый хендл
"попадает" в какую-либо "правильную" структуру. И результат
непредсказуем.
|