Работа с переменными¶
TODO
Добавить определение, что такое переменная.
Практически все операции над переменными перешли в ZScript с языков, подобных C.
В объявлении переменных сначала идёт тип данных для этой переменной, после — её название (идентификатор). Разрешается объявлять несколько переменных через запятую.
Идентификатор (имя) переменной может состоять из цифр, букв латинского алфавита и нижних подчёркиваний. Первой должна идти либо буква, либо подчёркивание — если в начале будет цифра, компилятор попытается понять слово как число.
тип_данных идентификатор [, идентификатор [, ...]];
// Например:
int health;
double progress100;
bool alreadyProcessed;
bool _initialized;
Числовые операции¶
В ZScript представлены базовые операции арифметики и элементарной алгебры: сложение, деление, деление по модулю (получение остатка), взятие абсолютного значение и так далее. Их поддерживают все встроенные целочисленные и вещественные типы данных.
Арифметика и стандартные операторы¶
Положим, что a, b и x — любые числовые переменные.
a + b: сложение;a - b: вычитание;a * b: умножение;a / b: деление;a % b: деление по модулю (взятие остатка). Возможно только в случае, еслиb— целочисленная;a++: постинкремент (прибавление единицы к значению переменной);a--: постдекремент (вычитание единицы от значения переменной);++a: преинкремент (прибавление единицы к значению переменной до остальных операций);--a: предекремент (вычитание единицы от значения переменной до остальных операций);
Несмотря на то, что в ZScript для чисел с плавающей точкой определён и инкремент, и декремент, использовать эти операции рекомендуется только для int.
Функции округления (только для чисел с плавающей точкой)¶
Округление возвращает вещественное число (double). Для получения целочисленного см. конвертация типов.
round( x ): округление до ближайшего целого;ceil( x ): округление до ближайшего целого вверх;floor( x ): округление до ближайшего целого вниз;
Алгебраические операции¶
abs( x ): модуль числа (абсолютное значение);sqrt( x ): извлечение квадратного корня;exp( x ): экспонента ("e^x");log( x ): натуральный логарифм (по основаниюe);log10( x ): логарифм по основанию 10;
Вспомогательные функции¶
max( a, b [, ...] ): получение максимального числа из заданного списка значений;min( a, b [, ...] ): получение минимального числа из заданного списка значений;clamp( x, lower, upper ): ограничивание значения переменнойxнижней границейlowerи верхнейupper(возвращается значение не меньшеlowerи не большеupper);
Операторы сравнения¶
a == b: проверка на равенство двух переменных (не только чисел);a != b: проверка на неравенство двух переменных;a > b: проверка на то, что числоaбольше числаb;a >= b: проверка на то, что числоaбольше либо равно числуb;a < b: проверка на то, что числоaменьше числаb;a <= b: проверка на то, что числоaменьше либо равно числуb.
Логические операторы¶
Часто применяются в условных операторах (операторах ветвления).
a && b: логическое "И" ("AND"). Возвращает истину тогда и только тогда, когда оба значения истинны;a || b: логическое "ИЛИ" ("OR"). Возвращает истину тогда, когда хотя бы одно значение истинно;!a: логическое "НЕ" ("NOT"). Возвращает значение, противоположное исходному. Грубо говоря, превращает "ложь" (0) в "истину" (1), а "истину" (1) в "ложь" (0).
"Таблицы" истинности:
0 && 0 == 0;
0 && 1 == 0;
1 && 0 == 0;
1 && 1 == 1;
0 || 0 == 0;
0 || 1 == 1;
1 || 0 == 1;
1 || 1 == 1;
!0 == 1;
!1 == 0.
Битовые операторы¶
Проводятся в двоичной системе счисления. Доступны только для целочисленных типов. Соответственно, a и b — целые.
a & b: битовое "И" ("AND"). Проводит операцию "И" для каждой соответственной пары битов изaиb;a | b: битовое "ИЛИ" ("OR"). Проводит операцию "ИЛИ" для каждой соответственной пары битов изaиb;a ^ b: битовое "ИСКЛЮЧАЮЩЕЕ ИЛИ" ("XOR"). Осуществляет проверку на неравенство для каждой соответственной пары битов изaиb;~a: битовое "НЕ" ("NOT"). Изменяет все биты на противоположные;a << b: битовое смещение влево. Смещает битыaнаbбитов влево с потерей старших;a >> b: битовое смещение вправо. Смещает битыaнаbбитов вправо с потерей младших.
Примеры результатов перечисленных операций на нескольких случайно выбранных числах (символ "₂" снизу после числа означает систему счисления по основанию "2" — собственно, представление в битах):
AND: OR: XOR:
11011010₂ 01010010₂ 01010010₂
& 01111100₂ | 10001010₂ ^ 11010011₂
-------- -------- --------
01011000₂ 11011010₂ 10000001₂
NOT: ~11000110₂ == 00111001₂
SHL: 00111001₂ << 2 == 11100100₂
11111000₂ << 3 == 11000000₂
SHR: 11010101₂ >> 3 == 00011010₂
Операторы присваивания¶
Присвоение любой переменной осуществляется через знак равенства:
a = b: присвоить переменнойaзначениеb;
Также существуют составные операторы присвоения:
a += b: присвоить переменнойaзначениеa + b;a -= b: присвоить переменнойaзначениеa - b;a *= b: присвоить переменнойaзначениеa * b;a /= b: присвоить переменнойaзначениеa / b;a %= b: присвоить переменнойaзначениеa % b;a <<= b: присвоить переменнойaзначениеa << b;a >>= b: присвоить переменнойaзначениеa >> b;a &= b: присвоить переменнойaзначениеa & b;a |= b: присвоить переменнойaзначениеa | b;a ^= b: присвоить переменнойaзначениеa ^ b;
Присвоение значений по умолчанию¶
В переменных внутри функций (см. 1.09 — "Методы") и в некоторых других случаях можно устанавливать значения по умолчанию. Далее в примерах будем этим активно пользоваться.
тип_данных идентификатор [= умолчание] [, идентификатор [= умолчание] [, ...]];
// Например:
int health = 100;
String showString = "Nothing to show now";
Также перед типом данных можно указывать ключевые слова-модификаторы (см. 2.01) — но в базовом разделе о них речи не пойдёт.
Обращение по указателю¶
Все неизвестные слова в этом подразделе можно принять как данность — чуть позже, в следующих статьях, они станут понятны.
Для того, чтобы обратиться к определённому полю/методу какого-либо сложного типа данных (экземпляра класса, структуры и так далее), необходимо после его идентификатора поставить точку и затем — название поля/метода. Эта конструкция уже встречалась и ранее:
Конвертация типов¶
Периодически при операциях появляется необходимость явно указывать выходной тип данных или использовать разные типы в одном выражении (простой пример: "1.33 + 1" — это "double + int"). ZScript умеет работать с ними, и следует понимать, как именно он их обрабатывает.
Сужение типов¶
В случае, если в операции участвует несколько типов, будет применяться сужение типов — все операнды сначала автоматически будут преобразованы в тот тип, который будет наиболее общим для всех из них.
Приоритет сужения (по порядку всё большего включения предыдущих) такой:
- Самый маловмещающий —
bool; - Затем
int; - Затем
double.
Так, в примере "true + 1" (bool + int) будет ответ "2" (int), а в "1.5 + 2" (double + int) — "3.5" (double). Порядок операндов не имеет значения, важен лишь приоритет сужения.
Явное преобразование простых типов¶
Иногда необходимо явно указать выходной тип операции. Для этого нужно "обернуть" выражение в скобки с указанием типа перед ними.
double b = 45.7;
// Сначала выполнится "round( b )" == "round( 45.7 )" == "46.0";
// Затем — преобразование в целочисленное ("46.0" → "46").
int a = int( round( b ) );
// "ceil( 45.7 / 0.63 + 45.7 )" == "ceil( 72.5397 + 45.7 )" ==
// == "ceil( 118.2397 )" == "119.0". После приведения к целому получится "119".
int c = int( ceil( b / 0.63 + b ) );
Надо отметить, что по почти таким же правилам это работает и не только с числами (например, см. 1.10, "Приведение типа класса").
Заметки о делении¶
При взятии остатка от деления по модулю ("%") происходит автоконвертация в int с округлением до ближайшего целого в сторону нуля. То есть, если делимое положительно, то округляется вниз, если отрицательно — вверх:
10.1 % 10 == 0 // То же, что и "10 % 10" — округление "10.1" вниз, в сторону нуля.
10.9 % 10 == 0 // То же, что и "10 % 10", снова округление вниз, "10.9" → "10".
10.1 % 11 == 10 // То же, что и "10 % 11" — округление "10.1" вниз, в сторону нуля.
10.9 % 11 == 10 // То же, что и "10 % 11", снова.
-10.5 % 10 == 0 // То же, что и "-10 % 10" — округление "10.5" вверх, в сторону нуля.
-10.5 % 11 == -10 // То же, что и "-10 % 11", снова.
Также стоит обратить внимание на деление целого и вещественного. Здесь также используется сужение типов:
double double1 = 7.5;
double double2 = 2.25;
int int1 = 8;
int int2 = 2;
/* Предсказуемое деление: */
double1 / double2; // Результат — "double".
int1 / double1; // Результат — "double".
double1 / int1; // Результат — "double".
/* Однако: */
int1 / int2; // Результат — "int".
Очень частая ошибка — как раз в делении целого на целое и ожидания в результате дробного. Сужение типов здесь не срабатывает, и, к примеру, "4 / 5" будет равно "0", а не "0.8"!
Для избегания подобного следует явно приводить какой-нибудь из аргументов к double():