WARNING:
В данной статье приводится уйма нестандартных решений.
Если вы новичок в Pawn или просто одержимы "чистотой" кода, рекомендую вам немедленно нажать Ctrl+W, Alt+F4 и для верности выполнить в командной строке "format C".
Дисклеймер: я не несу ответственности за то, что вы сделаете со своей системой с помощью вышеописанных инструкций. |
Всем привет, с вами снова Daniel_Cortez.
Сегодня я опишу недавно открытый мной приём по перегрузке функций в Pawn.
Думаю, некоторые из вас видели раньше т.н. перегрузку функций в таких языках программирования, как C++:
PHP код:
#include <cstdio>
// выводит на экран целочисленное значение
void Print(int arg)
{
printf("int: %d\n", arg);
}
// выводит вещественное значение
void Print(double arg)
{
printf("float: %f\n", arg);
}
// выводит логическое значение
void Print(bool arg)
{
// у функции printf нет спецификатора "%b" для bool, поэтому придётся
// реализовать вывод логических значений другим способом
printf("bool: %s\n", (arg) ? ("true") : ("false"));
}
int main(int argc, char **argv)
{
Print(1);
Print(1.0);
Print(true);
return 0;
}
Обратите внимание: в коде приведены 3 функции с одинаковым именем "Print", которые отличаются лишь параметрами.
Это и называется перегрузкой функций - возможность использовать несколько одноимённых функций с разным количеством параметров или параметрами разного типа.
Если откомпилировать такую программу и запустить, она выведет на экран:
Код HTML:
int: 1
float: 1.000000
bool: true
Почему я привожу пример на C++ ?
Да потому, что в Pawn всё не так просто - там нет нативной поддержки перегружаемых функций (так же, как и в C89, от которого и произошёл Pawn).
Тем не менее, можно имитировать эту возможность с помощью других штатных средств языка.
Итак, в Pawn мы не можем создавать функции с одинаковыми именами.
Но ведь мы можем сделать 3 функции с разными именами.
Затем добавим после них 4-ю, которая будет вызывать одну из первых трёх в зависимости от типа передаваемого параметра.
PHP код:
#include <a_samp>
// В Pawn фигурные скобки вокруг тела функции не обязательны,
// если оно состоит всего из 1 предложения.
stock PrintInt(i)
printf("int: %d", i);
stock PrintFloat(Float:f)
printf("float: %f", f);
stock PrintBool(bool:b)
printf("bool: %s", (b)?("true"):("false"));
// Основная функция.
// Фигурные скобки в типе параметра означают,
// что параметр может быть нескольких типов -
// в данном случае функция принимает параметры
// целочисленного типа (_), вещественные числа (Float)
// и логические значения (bool).
stock Print({_, Float, bool}:arg)
{
// Здесь функция будет вызывать PrintInt/PrintFloat/PrintBool.
}
// Здесь будем проверять работу Print(), по аналогии с примером на C++.
main()
{
print("--Function overloading emulation test--");
Print(1);
Print(1.0);
Print(true);
}
Следует заметить, что никаких типов данных в Pawn на самом деле нет: интерпретатор сам по себе поддерживает работу только с целыми числами.
К логическим переменным (bool) также применяются стандартные операции, применяемые к целым числам, а вещественные числа (Float) обрабатываются с помощью нативных функций, объявленных в float.inc.
Вместо типов компилятор использует теги - это и есть те самые "bool:", "Float:" и т.п. в объявлениях переменных, констант и функций.
Теперь сконцентрируем внимание на функции Print.
Как уже говорилось выше, она будет вызывать PrintInt/PrintFloat/PrintBool в зависимости от тега аргумента arg.
Но как именно мы будет определять тег аргумента?
Очень просто: для этого в Pawn есть оператор tagof, который определяет ID тега переменной, константы или функции.
Добавим в функцию Print ещё один параметр arg_type, который будет передаваться неявным образом.
И поскольку оператор tagof возвращает целочисленное значение, можно будет сделать вызовы PrintInt/PrintFloat/PrintBool из конструкции switch:
PHP код:
stock Print({_, Float, bool}:arg, arg_type=tagof(arg))
{
switch(arg_type)
{
case tagof(_:):
PrintInt(arg);
case tagof(Float:):
PrintFloat(arg);
case tagof(bool:):
PrintBool(arg);
}
}
Пробуем скомпилировать и напарываемся на кучу сообщений от компилятора:
Открыть/закрыть
Код:
test.pwn(16) : warning 220: expression with tag override must appear between parentheses
test.pwn(16) : error 020: invalid symbol name "_"
test.pwn(16) : error 029: invalid expression, assumed zero
test.pwn(18) : warning 217: loose indentation
test.pwn(18) : error 014: invalid statement; not in switch
test.pwn(18) : warning 215: expression has no effect
test.pwn(18) : error 001: expected token: ";", but found ":"
test.pwn(18) : error 029: invalid expression, assumed zero
test.pwn(18) : fatal error 107: too many error messages on one line
Compilation aborted.
Pawn compiler 3.10.20150531 Copyright (c) 1997-2006, ITB CompuPhase
6 Errors.
Всё дело в том, что в Pawn 3.2 есть баг, не позволяющий использовать tagof с именами тегов в качестве меток в switch (огромное спасибо ziggi, что сообщил об этом баге в комментариях).
К счастью, его можно обойти, заключив выражение с tagof в скобки:
PHP код:
stock Print({_, Float}:arg, arg_type=tagof(arg))
{
switch(arg_type)
{
case (tagof(_:)):
PrintInt(arg);
case (tagof(Float:)):
PrintFloat(arg);
case (tagof(bool:)):
PrintBool(arg);
}
}
Для удобства я выложу полное содержимое скрипта:
Открыть/закрыть
PHP код:
#include <a_samp>
stock PrintInt(i)
printf("int: %d", i);
stock PrintFloat(Float:f)
printf("float: %f", f);
stock PrintBool(bool:b)
printf("bool: %s", (b)?("true"):("false"));
stock Print({_, Float}:arg, arg_type=tagof(arg))
{
switch(arg_type)
{
case (tagof(_:)):
PrintInt(arg);
case (tagof(Float:)):
PrintFloat(arg);
case (tagof(bool:)):
PrintBool(arg);
}
}
main()
{
print("--Function overloading emulation test--");
Print(1);
Print(1.0);
Print(true);
}
Попробуем скомпилировать код: компилятор выплюнет варнинг "tag mismatch" на строке:
Похоже, следует перезаписать тег параметра функции тегом bool.
PHP код:
PrintBool(bool:arg);
(Интересно, почему такой же варнинг не выдаётся на аналогичном вызове PrintFloat()?)
Проверим код: на этот раз компилятор не выдаёт ни одной ошибки или предупреждения.
Теперь запустим скомпилированный скрипт на сервере SA:MP и проверим логи:
Код HTML:
--Function overloading emulation test--
float: 1065353216.000000
bool: 1
Это и есть то, что нам нужно? Не совсем...
Во-первых, сразу бросается в глаза непонятное значение в строке "float: ".
Во-вторых, настораживает строка "int: ". Вернее, её полное отсутствие -_-
При этом вызов Print() с целочисленным параметром присутствует в функции main().
Разберёмся со всем по порядку.
Помните, что компилятор не выдал варнинга "tag mismatch" на вызове PrintFloat()?
Всё потому, что без перезаписи тега компилятор воспринимает параметр arg, как целочисленный, и делает так, чтобы он конвертировался в вещественное число с помощью функции float() во время выполнения скрипта.
Таким образом, вместо PrintFloat(arg) получается PrintFloat(float(arg)). Именно функция float и выдаёт то самое непонятное число в логах.
Проверим эту догадку: заменим тег arg на Float в вызове PrintFloat():
PHP код:
case tagof(Float:):
PrintFloat(Float:arg);
И снова скомпилируем файл: никаких варнингов или ошибок. Запускаем...
Код HTML:
--Function overloading emulation test--
float: 1.000000
bool: 1
Минус одна проблема. Тем не менее, вывод целого числа всё ещё не работает, как надо.
Тут возможны 2 случая:- Компилятор как-то неправильно делает вызов функции PrintInt (или не делает его вообще).
- Внутри функции Print значение arg_type не совпадает с tagof(_:).
Первое точно не вариант. Нет, ну серьёзно, кто будет релизить компилятор, который даже не может правильно сделать простой вызов функции!?
Остаётся только второе. Проверим, добавив отладочный вывод предположительно несовпадающих значений через printf.
При этом вызовы Print с вещественным и логическим значениями в функции main лучше закомментировать, чтобы отладочный вывод не повторялся 3 раза подряд.
PHP код:
stock Print({_, Float}:arg, arg_type=tagof(arg))
{
/**/printf("%d %d", arg_type, tagof(_:));
switch(arg_type)
{
case (tagof(_:)):
PrintInt(arg);
case (tagof(Float:)):
PrintFloat(arg);
case (tagof(bool:)):
PrintBool(arg);
}
}
main()
{
print("--Function overloading emulation test--");
Print(1);
//Print(1.0);
//Print(true);
}
Пустой комментарий "/**/" оставлен специально, чтобы был варнинг из-за неправильной табуляции.
Так будет проще вспомнить про то, что нужно убрать строку с отладочным выводом.
Компилируем скрипт и запускаем сервер:
Код HTML:
--Function overloading emulation test--
0 --
Что мы видим: целочисленный тег имеет ID 0. Но что такое "--"?
На самом деле один из багов printf: функция не способна правильно вывести число -2147483648 (минимальное целочисленное значение в Pawn).
А значит наше предположение подтвердилось: ID тегов не совпадают (arg_type = 0, tagof(_:) = -2147483648).
Я не знаю, что это: баг компилятора или так задумано.
Но, как бы то ни было, нужно как-то обойти эту проблему.
Итак, оценим ситуацию:- arg не может иметь другого тега, кроме _, Float и bool.
- Значение параметра arg_type для целочисленного arg не совпадает с tagof(_:).
Основываясь на этом, можно вынести вызов PrintInt в ветку default внутри того же switch.
Также не забываем убрать отладочный вывод.
И ни в коем случае не забудьте раскомментировать вызовы Print в функции main.
В итоге функция Print будет выглядеть так:
PHP код:
stock Print({_, Float}:arg, arg_type=tagof(arg))
{
switch(arg_type)
{
case (tagof(Float:)):
PrintFloat(Float:arg);
case (tagof(bool:)):
PrintBool(bool:arg);
default:
PrintInt(_:arg);
}
}
Полное содержимое скрипта
PHP код:
#include <a_samp>
stock PrintInt(i)
printf("int: %d", i);
stock PrintFloat(Float:f)
printf("float: %f", f);
stock PrintBool(bool:b)
printf("bool: %s", (b)?("true"):("false"));
stock Print({_, Float}:arg, arg_type=tagof(arg))
{
switch(arg_type)
{
case (tagof(Float:)):
PrintFloat(Float:arg);
case (tagof(bool:)):
PrintBool(bool:arg);
default:
PrintInt(_:arg);
}
}
main()
{
print("--Function overloading emulation test--");
Print(1);
Print(1.0);
Print(true);
}
Компилируем и запускаем:
Код HTML:
--Function overloading emulation test--
int: 1
float: 1.000000
bool: true
Ну вот и всё, функция работает в точности так, как и было задумано.
Вот так в Pawn можно имитировать перегрузку функций. Вот только какой ценой...
P.S.: Эта статья была написана чисто из-за скуки во время пар.
Изначальной целью было не столько заставить пользователей использовать перегрузку функций в Pawn, сколько объяснить использование оператора tagof со всеми его достоинствами и недостатками.
Автор статьи: Daniel_Cortez
Копирование данной статьи на других ресурсах без разрешения автора запрещено!