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

Методы

ZScript по своим синтаксическим структурам очень напоминает C++ или C#: такое же объявление переменных, функций, похожее внутреннее наполнение. В случае, если Вы изучали Си, C++, C#, Java, JavaScript и другие Си-подобные языки, или же если Вы знаете Decorate с anonymous-функциями или ACS, разобраться с представлением кода на ZScript, вероятно, не составит труда.

Опциональность части синтаксиса

Во всех блоках кода ниже [Квадратные скобки] используются для того, чтобы обозначить необязательность написания того, что заключено внутри них.

Определения

Функция — описание какого-либо поведения, может принимать значения в качестве параметров и возвращать результат.

Можно попробовать провести параллель с функцией в математике. Это закон-замена одного значения другим, вычисленным по определённым правилам. А в программировании? Подпрограмма, которая может принимать на вход значения, что-то с ними внутри делать, и выдывать обратно уже иной, обработанный результат. Чего-то напоминает, не правда ли?

Метод в ООП — это функция, объявленная внутри структуры с данными. В ZScript функции вне классов/структур объявлены быть не могут, так что там всё — методы.

В объявлении функции/метода в языках программирования, и в ZScript в частности, должны присутствовать входные и выходные типы данных.

Синтаксис

Обычно следующего синтаксиса достаточно:

возвращаемый_тип название_метода( аргтип1 аргумент1[, аргтип2 аргумент2[, ...]]] ) {
    тело_метода
}
Или, в более общем случае,
возвращаемый_тип название_метода( [аргтип1 аргумент1 [= умолчание1][, аргтип2 аргумент2 [= умолчание2][, ...]]] ) {
    тело_метода

    [return возвращаемое_значение;]
}

возвращаемый_тип, аргтип1, аргтип2 и так далее должны быть любыми существующими поддерживаемыми типами данных, совершенно не обязательно одинаковыми. Целое число, вещественное число, булево значение, строка, 2D- и 3D-векторы, нативная структура вроде CVar (см. 2.01), указатель на любой объект, любой встроенный или собственный класс — всё это понимается парсером.

название_метода не должно быть идентичным названию какого-то другого метода или какой-либо переменной в том же или родительском классе (см. наследование в 1.11). Если будут две функции "Print()", то компилятор попросту не поймёт, какую из них нужно использовать в каждом месте вызова.

аргумент1, аргумент2 и так далее — аргументы, они же параметры функции, они же локальные переменные. Переданные откуда-либо снаружи значения в них можно использовать внутри тела метода. Сами внешние переменные при этом не меняются!

умолчание1, умолчание2 и так далее — значения локальных переменных по умолчанию. Параметры с ними — так называемые опциональные аргументы — должны идти после всех обязательных для передачи полей.

тело_метода — последовательность команд, которые необходимо выполнить в данном методе.

Также существует специальный тип void ("пусто"), символизирующий полное отсутствие переменной. Может использоваться либо на месте поля возвращаемый_тип, либо единственным значением в перечне аргументов. Не исключено одновременное использование и там, и там: "void SomeMethod( void ) {}".

return возвращаемое_значение; — эта конструкция завершает исполнение метода и возвращает указанное значение (если тип — не void). Она может использоваться более одного раза на функцию: например, в самом начале возвращая значение ошибки при неверных входных параметрах, а в конце — нормальный результат. Если тип возвращаемого значения равен void, то, во-первых, синтаксис конструкции вырождается до простого "return;", так как у пустоты не может быть значения; и, во-вторых, её можно вообще нигде не ставить.

Вместо значения void внутри скобок (на месте списка аргументов) можно оставить пустое пространство, смысл будет тем же самым.

Примеры

// Возвращает знак числа:
//  "1", если положительное;
//  "0", если равно нулю; и
//  "-1", в иных случаях (если отрицательное).
int sign( int value ) {
    if ( value > 0 )
        return 1;

    if ( value == 0 )
        return 0;

    return -1;
}

Пример метода без возвращаемого значения:

// Выводит в консоль указанное сообщение (<message>).
// Если сообщение не критическое (<fatal> == false), то цвет сообщения будет серым, иначе — красно-жёлтым.
void MyLog( String message, bool fatal = false ) {
    if ( fatal )
        console.printf( TEXTCOLOR_GRAY .. "MyLog(): " .. message );
    else
        console.printf( TEXTCOLOR_RED .. "!!! MyLog() fatal\c-: " .. TEXTCOLOR_FIRE .. message );
}

// Вызов:

MyLog( "Just message." );
MyLog( "An error! Maybe...", true );
MyLog( "Another non-fatal text.", false );

Два абсолютно равнозначных метода:

double RandomFrom0To1_a() {
    return FRandom( 0.0, 1.0 );
}

double RandomFrom0To1_b( void ) {
    return FRandom( 0.0, 1.0 );
}

Дополнительный функционал ZScript

ZScript имеет несколько интересных особенностей, связанных с вызовом функций.

Возможно, если Вы не знакомы с другими языками программирования, при первом прочтении стоит этот расширенный раздел опустить, вернувшись к нему позже.

Для описания статических методов см. 1.12.

Возврат более чем одного значения

Не всегда реально обойтись одним возвращаемым значением, особенно если учесть комментарий m8f в статье 1.10. В ZScript существует механизм, позволяющий возвращать более одного типа актора. Рассмотрим его синтаксис со всех сторон.

Синтаксис:

  • В объявлении метода — все необходимые возвращаемые типы через запятую;
  • При возвращении из метода — также через запятую, "return возвр_знач1, возвр_знач2 [, возвр_знач3 [, ...]]";
int, int, double GetHealthExtended( void ) {
    double healthpercent = health / double( spawnhealth );

    return spawnhealth, health, healthpercent;
}
  • В месте вызова: либо все возвращаемые значения (в том числе и те, которые сейчас не нужны) должны быть приняты через оператор присваивания в "псевдомассив", либо только первое, и тогда квадратные скобки будут не нужны. Здесь — пример с возвращением всего перечня:
int hp, spawnhp;
double hppercent;

[ hp, spawnhp, hppercent ] = something.GetHealthExtended();

console.printf( "Health percent: " .. hppercent );

Именованные параметры

Иногда требуется передать в функцию какой-либо опциональный аргумент, который находится где-то в самом конце. Для примера возьмём встроенную функцию "A_SpawnItemEx()" (создать на уровне указанного актора с заданными координатами, скоростью, наклоном, параметрами и TID. И да, в ней как раз используется возврат двух значений сразу):

Actor, bool A_SpawnItemEx( class<Actor> itemtype, double xofs = 0, double yofs = 0, double zofs = 0, double xvel = 0, double yvel = 0, double zvel = 0, double angle = 0, int flags = 0, int failchance = 0, int tid = 0 );

Пусть у нас будет создаваться дымок от выстрела. И поэтому, например, нам необходимо задать этому актору только вертикальную скорость и шанс появления — все остальные аргументы в данном случае нам просто не нужны.

Для того, чтобы добраться до них, мы можем, конечно, методично переписывать значения по умолчанию. Но читать это довольно трудно:

// Устанавливаем только "xvel" и "failchance":
A_SpawnItemEx( "ExampleShootSmoke", 0, 0, 0, 0, 0, 3.5, 0, 0, 63 );

Но проще воспользоваться передачей именованного аргумента:

A_SpawnItemEx( "ExampleShootSmoke", xvel: 3.5, failchance: 63 );

Благодаря этому сразу становится видно, какие конкретно параметры мы передаём.

Передача переменной, а не её значения

Чем-то похоже на ссылку в C++ и на передачу указателя на переменную в C (ну, плюс-минус).

Периодически возникает необходимость оперировать не над переданными данными, а над тем, что хранит эти самые данные; простой учебный пример — обмен местами значений в двух переменных. Для такого случая в ZScript существует ключевое слово "out", прописывающееся перед типом переменной:

void ExchangeInts( out int var1, out int var2 ) {
    int temp = var1;
    var1 = var2;
    var2 = temp;
}

В этом случае поменяются не локальные копии переменных, а то, что было передано:

int a = -3;
int b = 9876;

console.printf( "a: " .. a .. ", b: " .. b ); // "a: -3, b: 9876";
ExchangeInts( a, b );
console.printf( "a: " .. a .. ", b: " .. b ); // "a: 9876, b: -3".

По одной и той же причине на этот функционал есть два ограничения: во-первых, на объявленных с этим ключевым словом переменных нельзя использовать значения по умолчанию, во-вторых, нельзя передавать по такому аргументу константное значение.

void FalseExchanging( out int var1, out int var2 = 5 ) { // Ошибка компиляции.
    int temp = var1;
    var1 = var2;
    var2 = temp;
}

ExchangeInts( 5, 3 ); // Ошибка компиляции: у констант нет адреса, откуда конкретно движку брать значение, куда записывать?

Вариадические аргументы

Недоступны для объявления в пользовательских функциях, но рассмотреть стоит. Их смысл — передача нефиксированного количества аргументов.

Синтаксис довольно прост: в модификаторах метода (см. 2.09) пишется "vararg", после всех передаваемых аргументов — ", ...". Встречается такая запись, например, в методе "String.Format()" или в небезызвестном "console.printf()":

struct StringStruct native {
    // <...>
    native static vararg String Format( String fmt, ... );
    // <...>
}

struct Console native {
    // <...>
    native static vararg void Printf( String fmt, ... );
    // <...>
}