Перейти к содержанию

Работа с переменными

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) — но в базовом разделе о них речи не пойдёт.

[модификаторы] тип_данных идентификатор [, идентификатор [, ...]];

Обращение по указателю

Все неизвестные слова в этом подразделе можно принять как данность — чуть позже, в следующих статьях, они станут понятны.

Для того, чтобы обратиться к определённому полю/методу какого-либо сложного типа данных (экземпляра класса, структуры и так далее), необходимо после его идентификатора поставить точку и затем — название поля/метода. Эта конструкция уже встречалась и ранее:

// Обращение к методу "printf()" встроенной структуры "console":
console.printf( "Logged." );

Конвертация типов

Периодически при операциях появляется необходимость явно указывать выходной тип данных или использовать разные типы в одном выражении (простой пример: "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():

int a = 7;
int b = 10;

double result = a / double( b );  // В ответе, как и предполагается, "0.7".

// Для сравнения:
double false_result = a / b;      // В ответе — неверный "0.0".