Введение в программирование на SourcePawn. Часть 4.

Сообщения
207
Реакции
420
Помог
10 раз(а)
Введение в программирование на SourcePawn. Функции, их типы.

Из коробки, SourceMod предоставляет нам довольно мощную библиотеку функций. Мы можем работать с базами данных, файлами, другими плагинами, и многое другое. Все эти функции Вы можете увидеть в официальном API. Мы обязательно рассмотрим некоторые "библиотеки".
Помимо официальной библиотеки, мы можем создавать свои функции, или пользоваться функциями других плагинов. Свои функции обычно используются для избежания повторяемого кода, а функции других плагинов - для интеграции. К примеру, мы можем добавить свой пункт в официальное меню админки SourceMod. К этому всему мы обязательно вернёмся ещё. Сейчас мы рассмотрим возможность создавать свои функции.

Выделяются четыре возможных типа функций:
  • private - приватные функции. Обычно при объявлении таких функций просто пишется возвращаемый тип данных и её название.
    Код:
    void DoSomeAction() {
      // code...
    }
  • public - публичная функция. В отличие от приватных, её можно вызвать извне, зная только её название. Для этого используется функция GetFunctionByName(), которая возвращает уже указатель на функцию.
    Ещё компилятор не "жалуется" на неиспользуемые получаемые переменные в этом типе функций.
    Код:
    public void DoSomeAction() {
      // code...
    }
  • stock - тоже приватная функция, но если не используется - не включается компилятором в конечный "бинарник".
    Код:
    stock void DoSomeAction() {
      // code...
    }
  • static - ещё одна разновидность приватной функции. Я редко видел, чтобы этот тип вообще применяли, но в целом он даёт нам возможность запретить её вызывать сторонним файлам, которые подключают файл с этой функцией.
    Код:
    // some_include.inc
    static void DoSomeAction() {
      // code...
    }
    
    // file1.sp
    #include <some_include>
    
    public void OnPluginStart() {
      DoSomeAction(); // сгенерирует ошибку, что функция "не существует"
    }

Все эти типы можно так же применять на переменных.
Код:
int g_iSomeVar; // приватная переменная
public int g_iSomeVar; // публичная переменная (расширения и ядро SourceMod могут её редактировать без адреса, зная только её имя)
stock int g_iSomeVar;
static int g_iSomeVar;
Сформируем небольшое правило о том, как надо правильно объявлять функцию:
  • Сначала определяем тип функции. Если её будет вызывать в последствии само ядро SM (например, функция будет служить каллбеком для команды) или сторонние плагины, делаем тип public. В противном случае - private/stock.
  • Указываем тип возвращаемых данных. Если функция ничего не возвращает - пишем void (пустота).
  • В скобках перечисляем типы и названия аргументов, которые должна принимать функция. Если функция ничего на вход не принимает - оставляем скобки пустыми.
  • Открываем тело функции, которое является блоком (см. раздел 3).
  • Пишем код.
  • Если функция должна что-то возвращать - используем return в нужных местах.

Пример простейшей функции. Ищет игрока указанной команды, и возвращает первого попавшегося. На вход принимает номер команды и "бул"-переменную, должен ли быть игрок живым. Если поиск не удался, возвращает 0.
Код:
stock int GetClientFromTeam(int iTeam, bool bShouldIsPlayerAlive = false) {
  for (int i = MaxClients; i != 0; --i)
    if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) == iTeam && (!bShouldIsPlayerAlive || IsPlayerAlive(i)))
      return i;
  return 0;
}
Здесь применён подход итерации по всем игрокам. Мы обязательно к этой теме ещё вернёмся в разделе "синтаксиса".

"Ссылки" на переменные
Пожалуй, стоит рассмотреть ещё одну очень удобную штуку. Когда мы передаём некоторые переменные, виртуальная машина выполняет копирование значений. То есть, в вызванной функции мы можем их редактировать, но их значения из места, откуда функция вызвана, отредактированы не будут.
Исключения составляют массивы (строки - массивы символов). Массивы сразу передаются указателями, потому виртуальная машина никогда не знает точного размера оных (это, кстати, та самая причина, по которой функции, работающие с массивами, требуют указывать их размеры).
Это очень удобно, если не хочется плодить тысячи переменных, и сэкономить памяти (которой, вообще-то, не так уж и много):
Код:
public void OnCheckClientTimer(Handle hTimer, int iClient) {
  if ((iClient = GetClientFromUserId(iClient)) == 0)
    return;

  // some logic here...
}
Но что делать, если мы наоборот хотим, чтобы данные редактировались? Для этого ещё в Си была такая штука, как "ссылка". То есть вместо копии значения, мы передаём функции адрес нашей переменной, и все изменения, производимые над переменной, применяются и в "родительских", оригинальных переменных.
Записывается это в объявлении переменных с помощью знака "амперсанда" (&). На старом синтаксисе - перед типом переменной, а на новом - перед самим именем:
Код:
// старый синтаксис
public GetData(iClient, &Float:flConnectionTime, &iConnectedFrom) { /** ... */ }

// новый синтаксис
public void GetData(int iClient, float &flConnectionTime, int &iConnectedFrom) { /** ... */ }
Хочется отметить ситуации, когда применение "ссылочных переменных" излишне.
  • Получение значения из вызванной функции не требуется
  • Handle (он сам по себе уже указатель)
  • Функция и так возвращает нужное значение через оператор return

Когда обычно применяются "ссылочные переменные"? Чаще всего, в событиях, на которые можно как-то повлиять: OnTakeDamage, например. Мы можем отредактировать урон или игрока, который его нанёс. И не только.
Ещё видел такое использование в официальной "библиотеке" SM:
Код:
/**
 * Retrieves a value in a Map.
 *
 * @param map		Map Handle.
 * @param key		Key string.
 * @param value		Variable to store value.
 * @return			True on success.  False if the key is not set, or the key is set 
 *					as an array or string (not a value).
 * @error			Invalid Handle.
 */
native bool GetTrieValue(Handle map, const char[] key, any &value);
Тоже, в принципе, вариант.

Локальные статические переменные
Хотя это частично и не относится к теме этой главы, тоже интересно. Как правильно отметил ниже в комментариях wopox1337, в SourcePawn так же есть "локальные статические" переменные, и работают они как глобальные частично.
Для этого "подраздела" воспользуемся примером кода с локальными и глобальными переменными из главы 3:
Код:
int g_iData;

public void OnPluginStart() {
  g_iData++;
  DoLogic();

  g_iData++;
  DoLogic();
}

void DoLogic() {
  int iData;
  iData++;

  PrintToServer("%d %d", g_iData, iData);
}
Я его слегка почистил от комментариев, но роли особо не играет.
Если мы к локальной переменной iData добавим "кейворд" static перед типом переменной, мы получим локальную переменную, значение которой не обнуляется при завершении функции.
Код:
void DoLogic() {
  static int iData;
  iData++;

  PrintToServer("%d %d", g_iData, iData);
}
В таком случае, мы получим немного иной "выхлоп" при запуске плагина:
Код:
1 1
2 2
 
Последнее редактирование:
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
http://www.c-cpp.ru/books/staticheskie-lokalnye-peremennye - к локальным переменным это не относится в SourcePawn? Они не хранятся долгосрочно, подобно глобальным?
20 Мар 2018
запретить её вызывать сторонним файлам, которые подключают файл с этой функцией.
ибо, как я понял, рассмотрен только метод применения в качестве глобальной переменной
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
к локальным переменным это не относится в SourcePawn?
Относится. Так же, как и в C++, мы при "повторном" объявлении статической переменной, получаем переменную с значением, которое было при завершении функции.

Дополнил материал по "ссылочным переменным".
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Добавил немного информации касательно "локальных статических переменных".
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
18 Май 2018
Когда мы передаём некоторые переменные, виртуальная машина выполняет копирование значений
про стак позже? или вообще не затронется?
18 Май 2018
Когда обычно применяются "ссылочные переменные"?
так же дополнить, пример, где объявлена функция, в которой несколько аргументов, и к примеру, лишь один из них не ссылочный. (такие функции используются для получения данных в несколько переменных. Как возврат, только нескольких значений сразу.)
 
Последнее редактирование:
Сообщения
207
Реакции
420
Помог
10 раз(а)
про стак позже? или вообще не затронется?
Про устройство - да, позже. Это будет в более подробном разборе, что и как работает.
18 Май 2018
так же дополнить, пример, где объявлена функция, в которой несколько аргументов, и к примеру, лишь один из них не ссылочный.
Дополню. Это надо найти такой пример, или придумать.
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
или придумать.
C++:
player_spawn(pPlayerId)
{
    new Age, Weight, Speed;
   
    Player__GetProperties(pPlayerId, Age, Weight, Speed);

// print them ...
}

Player__GetProperties(id, &Age, &Weight, &Speed)
{
    Age = Player_GetAge(id);
    Weight = Player_GetWeight(id);
    Speed = Player_GetSpeed(id);
}
18 Май 2018
желательно с всеми возможными типами референса.
 

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

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу