Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Страница 1 из 3 1 2 3 ПоследняяПоследняя
Показано с 1 по 10 из 29
  1. #1
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±

    Перегрузка функций в Pawn (tagof)

    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 argcchar **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({_Floatbool}: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({_Floatbool}:argarg_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}:argarg_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}:argarg_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" на строке:
    PHP код:
    PrintBool(arg); 
    Похоже, следует перезаписать тег параметра функции тегом 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 случая:
    1. Компилятор как-то неправильно делает вызов функции PrintInt (или не делает его вообще).
    2. Внутри функции Print значение arg_type не совпадает с tagof(_:).

    Первое точно не вариант. Нет, ну серьёзно, кто будет релизить компилятор, который даже не может правильно сделать простой вызов функции!?
    Остаётся только второе. Проверим, добавив отладочный вывод предположительно несовпадающих значений через printf.
    При этом вызовы Print с вещественным и логическим значениями в функции main лучше закомментировать, чтобы отладочный вывод не повторялся 3 раза подряд.
    PHP код:
    stock Print({_Float}:argarg_type=tagof(arg))
    {
        
    /**/printf("%d %d"arg_typetagof(_:));
        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).

    Я не знаю, что это: баг компилятора или так задумано.
    Но, как бы то ни было, нужно как-то обойти эту проблему.
    Итак, оценим ситуацию:
    1. arg не может иметь другого тега, кроме _, Float и bool.
    2. Значение параметра arg_type для целочисленного arg не совпадает с tagof(_:).

    Основываясь на этом, можно вынести вызов PrintInt в ветку default внутри того же switch.
    Также не забываем убрать отладочный вывод.
    И ни в коем случае не забудьте раскомментировать вызовы Print в функции main.

    В итоге функция Print будет выглядеть так:
    PHP код:
    stock Print({_Float}:argarg_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}:argarg_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

    Специально для Pro-Pawn.ru
    Копирование данной статьи на других ресурсах без разрешения автора запрещено!
    Последний раз редактировалось Daniel_Cortez; 25.07.2016 в 21:05. Причина: --
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  2. 21 пользователя(ей) сказали cпасибо:
    $continue$ (09.05.2015) Brendan (21.08.2015) Camelot (30.06.2015) Desulaid (08.05.2015) Edwin (26.11.2016) Gabriel (12.05.2015) Geebrox (27.07.2016) iWors (29.07.2016) L0ndl3m (08.05.2015) Mazzilla (12.05.2015) Mr.DeViLsS (09.05.2015) MR_BEN (09.05.2015) Nurick (19.08.2015) Osetin (09.05.2015) oukibt (18.04.2021) Prolific (22.03.2016) Salvacore (08.05.2015) Smile (16.08.2015) Web (09.05.2015) XakeP (22.07.2015) ^_^ (19.08.2015)
  3. #2
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    До изучение С++ не понимал, что это...
    Теперь все понятно.

  4. #3
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    А прототип шаблона функций из С++ возможен?

  5. #4
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    forward же...
    PHP код:
    forward SomeFunc();

    main()
    {
        
    SomeFunc();
    }

    SomeFunc()
    {
        
    /* Do something. */

    Стоит заметить, что в более ранних версиях Pawn ключевого слова forward не было и функции предварительно объявлялись без него.
    В Pawn 3.2 тоже можно объявлять функции старым методом, эту возможность оставили для совместимости:
    PHP код:
    SomeFunc();

    main()
    {
        
    SomeFunc();
    }

    SomeFunc()
    {
        
    /* Do something. */

    Лично я предпочитаю второй способ, он выглядит более си-подобным.
    Хоть он и считается устаревшим, куй вряд ли станет переводить SA:MP на Pawn 4.0 - из существующих сейчас скриптов ни один не сможет работать с новой версией языка, уж столько там изменений.
    Да и Zeex со своей версией компилятора вряд ли станет нарушать совместимость с Pawn 3.2 (единственная фича, которую он убрал из компилятора - автоматическая защита от повторного включения инклуда, её он убрал, чтобы компилятор одинаково работал под разными ОС), так что нет никакой причины избегать того способа.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  6. #5
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    forward же...
    PHP код:
    forward SomeFunc();

    main()
    {
        
    SomeFunc();
    }

    SomeFunc()
    {
        
    /* Do something. */

    Стоит заметить, что в более ранних версиях Pawn ключевого слова forward не было и функции предварительно объявлялись без него.
    В Pawn 3.2 тоже можно объявлять функции старым методом, эту возможность оставили для совместимости:
    PHP код:
    SomeFunc();

    main()
    {
        
    SomeFunc();
    }

    SomeFunc()
    {
        
    /* Do something. */

    Лично я предпочитаю второй способ, он выглядит более си-подобным.
    Хоть он и считается устаревшим, куй вряд ли станет переводить SA:MP на Pawn 4.0 - из существующих сейчас скриптов ни один не сможет работать с новой версией языка, уж столько там изменений.
    Да и Zeex со своей версией компилятора вряд ли станет нарушать совместимость с Pawn 3.2 (единственная фича, которую он убрал из компилятора - автоматическая защита от повторного включения инклуда, её он убрал, чтобы компилятор одинаково работал под разными ОС), так что нет никакой причины избегать того способа.
    Не forward, в Pawn придется указывать тип.

  7. #6
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    Не forward, в Pawn придется указывать тип.
    О чём вы? В Pawn у функций, возвращающих целые числа, не нужно указывать тип.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  8. #7
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    О чём вы? В Pawn у функций, возвращающих целые числа, не нужно указывать тип.
    А если string или float?
    Причём параметры могут быть разными (int, float, string, char, double)
    Ах да, я не про возвращаемое значения, а тип в аргументах...
    Последний раз редактировалось $continue$; 21.07.2015 в 23:09.

  9. #8
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    А если string или float?
    PHP код:
    Float:GetRandomFloat()
        return 
    Float:(random(cellmax) + random(cellmax)); 
    PHP код:
    GetRandomString(buffer[], size sizeof(buffer))
        for (
    buffer[--size] = EOSsize != 0buffer[--size] = random(ucharmax-1)+1) {} 
    PHP код:
    forward [MAX_PLAYER_NAME 1GetPlayerNameAlt(playerid);
    GetPlayerNameAlt(playerid)
    {
        new 
    name[MAX_PLAYER_NAME 1];
        
    GetPlayerName(playeridnamesizeof(name));
        return 
    name;

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

    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    Причём параметры могут быть разными (int, float, string, char, double)
    Ах да, я не про возвращаемое значения, а тип в аргументах...
    А разве не это рассматривалось в уроке (на примере одного параметра) ?
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  10. #9
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    Не, ну это перегрузка
    https://ru.m.wikipedia.org/wiki/%D0%...%D1%8B_C%2B%2B

  11. #10
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    Не, ну это перегрузка
    https://ru.m.wikipedia.org/wiki/%D0%...%D1%8B_C%2B%2B
    Ах вот вы про какие прототипы...
    Скажу честно, слабо знаком с ними, ибо в C++ сильно не углублялся. Но сомневаюсь, что есть что-то похожее в Pawn - там нет классов.
    А для параметров переменного типа вполне хватит описанного в уроке трюка с tagof. Разве что, если параметров несколько, то они должны располагаться в порядке "параметр1, параметр2, ..., параметрN, tagof(параметр1), ..., tagof(параметрN)" - таким образом можно сделать скрытую передачу типов всех параметров. Единственный минус - нельзя передавать массивы (разве что с помощью #emit передавать указатели на массивы и делать собственную систему идентификации типов, чтобы отличать массивы от обычных переенных, но это уже явно перебор).
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

 

 
Страница 1 из 3 1 2 3 ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •