Русское сообщество по скриптингу

Введение в SourcePawn программирование

Статьи или фрагменты кода для новичков и уже опытных скриптеров по SourceMod.
Правила форума
1. Запрещено материться и оскорблять других участников форума.
2. Запрещен флуд, оффтоп, дабл постинг во всех разделах форума, кроме раздела "Болтовня".
3. Запрещено взламывать сайт/форум или наносить любой вред проекту.
4. Запрещено рекламировать другие ресурсы.
5. Запрещено создавать темы без информативного названия. Название темы должно отображать ее смысл.

В данном разделе форума разрешено создавать темы, касающие только обучающему материалу по SourceMod.

Введение в SourcePawn программирование

Сообщение DJ_WEST » 03 сен 2009, 10:36

Источник: wiki.alliedmods.net
Перевод: Frenzzy

Это руководство призвано дать вам самые основные представления по написанию сприптов в SourcePawn. Pawn - это "скриптовый" язык используемый для внедрения функциональности в других программах. Это означает, что это не самостоятельный язык, как C++ или Java, и его элементы будут отличаться в различных приложениях. SourcePawn - это вариация языка Pawn, используемая в SourceMod.

Это руководство не расскажет вам, как писать SourceMod плагины; оно предназначено для получения общих представлений о синтаксисе и семантике этого языка.

Особенности языка
Pawn может показаться очень похожим на другие языки программирования, например C, но Pawn от них фундаментально отличается. Не столь важно, чтобы вы сейчас же поняли его отличия, но они понадобятся, если вы уже знаете один из языков программирования:
  • Pawn не собирает мусор
    Pawn, как язык, не имеет встроенных ресурсов памяти, и потому он не мусорит. Если функция выделит память, то вы отвечаете за её освобождение.
  • Pawn не объектно-ориентированный язык
    Pawn является процедурным и полагается на подпрограммы. Также у него нету C подобных структур.
  • Pawn не функциональный
    Pawn является процедурным и не поддерживает функции "лямбды" (Lambda), поздние присвоения и все то, что можно найти в языках высшего уровня, таких как Phyton и Ruby.
  • Pawn однопоточный
  • Pawn не интерпретируемый
    Ну, почти. Он интерпретируется на очень низком уровне. Вы должны скомпилировать код, из которого получится бинарный файл. Эта программа будет работать на той платформе, которую использует сервер. Это ускоряет загрузку и позволяет легче находить ошибки.

Этот язык был выпущен ITB CompuPhase. Язык разработан для устройств низкого уровня и таким образом конечные программы очень маленькие по размеру и очень быстрые.

Идентификаторы/ключевые слова
Идентификаторы представляет собой набор букв, цифр и/или нижнего подчеркивания, что представляет собой нечто уникальное. Идентификаторы вводятся с учетом регистра (в отличие от PHP, где иногда это не требуется). Идентификаторы не начинаются с какого-либо специального символа, но они должны начинаться с буквы.

Есть несколько зарезервированных символов, которые имеют особое значение. Например, if, for и return специальные конструкции в языке, которые будут описаны позднее. Они не могут быть использованы в качестве названий идентификаторов.

Переменные
Существует несколько важных конструкций, которые вы должны знать, прежде чем приступить к написанию сценария. Во-первых, это переменные. Переменная - это идентификатор, который содержит данные. Например, переменная "a" может содержать числа "2", "16", "0" и так далее. Переменные создаются для хранения данных внутри программы. Переменные должны быть объявлены до их использования, с помощью ключевого слова "new". Данные можно присвоить переменной, используя знак равенства (=). Пример:
Код: Выделить всё

new a
, b, c, d;
 
= 5;
= 16;
= 0;
= 500;
 


В SourcePawn, переменные бывают двух типов:
  • Однострочные (могут содержать только произвольные числовые данные), как показано выше
  • Многострочные (могут содержать целый ряд текстовых символов)

Однострочные могут содержать 32 бита цифровых данных. Многострочные - последовательный список из UTF-8 символов. Однострочные не имеет своего типа, однако они могут быть маркированы(tagged). Тег позволяет вам указывать, где определенную ячейку можно использовать. Типичные теги:
  • (пусто), или _ - Нет тега. Обычно используют для целых чисел (Integers).
  • Float - используют для чисел с плавающей точкой (небольших).
  • bool - используют для хранения значений true (истина) или false (ложь).

Со строками все по другому, они будут рассмотрены далее.

Объявления
Примеры разных правильных объявлений переменных:
Код: Выделить всё

new a 
= 5;
new Float:= 5.0;
new bool:= true;
new bool:= 0;      // работает, поскольку 0 равно false (ложь)
 

Неправильные объявления переменных:
Код: Выделить всё
new a = 5.0;         // несоответствие тегов. 5.0 должо быть с тегом Float
new Float:= 5;     // несоответствие тегов. 5 должно быть без тега.    

Если переменная не определена в объявлении, то ее значение станет 0:
Код: Выделить всё
new a;        // значение 0
new Float:b;  // значение 0.0
new bool:c;   // значение false    


Присвоение
Переменным могут быть присвоены данные после создания. Пример:
Код: Выделить всё

new a
, Float:b, bool:c;
 
= 5;
= 5.0;
= true;
 


Комментарии
Примечания и любой текст, который пишется после "//" считается "комментарием", а не фактическим кодом. Есть два стиля комментариев:
  • // - двойная косая черта, всё, следующее после этой строки, игнорируется
  • /* */ - много-строчный комментарий, весь текст, внутри звездочек, игнорируются

Массивы
Вы можете группировать код в виде "массивов", разделенных { и }. Это фактически создает возможность работать с целым массивом как с одним оператором. Например:
Код: Выделить всё
{
   здесь;
   какой-то;
   код;
}
 


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

Массив объявляется с помощью квадратных скобок. Вот некоторые примеры массивов:
Код: Выделить всё
new players[32];     // Набор из 32 однострочных (числовых) данных
new Float:origin[3]; // Набор из 3 чисел с плавающей точкой    


По умолчанию, массивам присваиваются нули. Вы можете присвоить им разные значения, однако:
Код: Выделить всё
new numbers[5] = {1, 2, 3, 4, 5};       // В numbers будут храниться значения 1, 2, 3, 4 ,5
new Float:origin[3] = {1.0, 2.0, 3.0};  // В origin будут храниться значения 1.0, 2.0, 3.0    


Вы можете оставить массив без размера, если вы собираетесь заранее присвоить ему данные. Например:
Код: Выделить всё
new numbers[] = {1, 3, 5, 7, 9}; 

Компилятор будет автоматически делать вывод о том, что вы хотите получить массив размером 5.

Использование массива равносильно использованию обычной переменной. Единственное отличие состоит в том, что он должен быть индексируемым. Индексирование массива означает присутствие возможности выбрать элемент, который вы хотите использовать.

Вот пример кода с использованием индексов:
Код: Выделить всё

new numbers
[5], Float:origin[3];
 
numbers
[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
origin[0] = 1.0;
origin[1] = 2.0;
origin[2] = 3.0;
 


Заметим, что индекс это текст, который находится в квадратных скобках. Индекс всегда начинается с нуля. То есть, если массив имеет N элементов, его действительный индекс от 0 до N-1. Доступ к данным с индексами работает так же, как с обычной переменной.

Использование неверного индекса вызовет ошибку. Например:
Код: Выделить всё
new numbers[5];
 
numbers
[5] = 20; 

Это может выглядеть верно, но число 5 не является допустимым индексом. Наибольшим значением индекса является число 4.

Вы можете использовать любые выражения, как индекс. Например:
Код: Выделить всё

new a
, numbers[5];
 
= 1;                   // сделает a = 1
numbers[a] = 4;          // сделает numbers[1] = 4
numbers[numbers[a]] = 2; // сделает numbers[4] = 2
 


Строки
Строки являются удобным способом хранения текста. Символы хранятся в массиве. Строка ограничивается нулевым символом или 0. Без нулевого символа, Pawn не знает, где остановить чтение строки. Все строки в SourcePawn используют кодировку UTF-8.

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

Примечание для специалистов: они фактически не однострочные. SourcePawn использует 8-битные строки для хранения массивов в качестве оптимизации. Это и есть то, что делает строки типом, а не меткой.

Строки были созданы почти в равной степени и для массивов. Например:
Код: Выделить всё
new String:message[] = "Hello!";
new String:clams[6] = "Clams";
 


Это равносильно следующему:
Код: Выделить всё
new String:message[7], String:clams[6];
 
message
[0] = 'H';
message[1] = 'e';
message[2] = 'l';
message[3] = 'l';
message[4] = 'o';
message[5] = '!';
message[6] = 0;
clams[0] = 'C';
clams[1] = 'l';
clams[2] = 'a';
clams[3] = 'm';
clams[4] = 's';
clams[5] = 0;
 


Хотя строки редко инициализируют таким образом, очень важно помнить о концепции нулевого символа, который свидетельствует о конце строки. Компилятор и большинство SourceMod функций будут автоматически остановлены нулевым символом, поэтому он является очень важным при манипулировании строками напрямую.

Заметим, что строка должна быть заключена в двойных кавычках, а символ в одиночных.

Символы
Особенность текста может быть использована в любой строке или однострочной переменной. Например:
Код: Выделить всё
new String:text[] = "Crab";
new clam;
 
clam 
= 'D';         // Устанавливает однострочной переменной значение 'D'
text[0] = 'A';      // Меняет 'C' на 'A', сейчас получилось 'Arab'
clam = text[0];     // Устанавливает однострочной переменной значение 'A'
text[1] = clam;     // Меняет 'r' на 'A', сейчас получилось 'AAab'    


То, что вы не можете сделать, это соотнести символы массивов со строками. Внутреннее хранение отличается. Например:
Код: Выделить всё
new clams[] = "Clams";                       // Не верно, нужен тип String
new clams[] = {'C', 'l', 'a', 'm', 's', 0};  // Верно, но это НЕ СТРОКА.    


Функции
Следующим важным понятием являются функции. Функции идентификаторов или имен, которые выполняют действия. Это означает, что когда вы их активируете, они выполняют конкретную последовательность кода. Есть несколько типов функций, но все функции активируется одинаковым образом. "Вызов функции" является термином ссылающимся на функцию действия. Функция числовых переменных строятся так:
функция(<параметры>)


Примеры:
Код: Выделить всё
show(56);   // Активирует функцию "show" и присваивает ей число 56
show();     // Активирует функцию "show" без каких-либо данных, пустую
show(a);    // Активирует функцию "show" и присваивает ей переменную с данными переменной a    


Каждый фрагмент данных передаваемый вызываемой функции, называется параметр. Функция может иметь любое количество параметров (но есть "допустимый" предел в SourceMod: 32).

Существуют два типа вызова функции:
  • Прямой вызов - вы специально вызываете функцию в своем коде.
  • Обратный вызов - применение вызова функции в вашем коде, как если бы это было событием триггера (совокупность условий, инициирующих выполнение действия).

Существуют пять видов функций:
  • native - прямая, внутренняя функция, предусмотренная в приложении.
  • public - функция обратного вызова, что делает её видимой для приложения и других сценариев.
  • normal - нормальная функция, которую вы можете только вызвать.
  • stock - нормальная функция, которая если не используется, то не компилируется.
  • forward - функция представляет собой глобальное событие, если вы его вызвали, то она будет исполняться.

Весь код в Pawn должен существовать в функциях. Это основное отличие от языков, таких как PHP, Perl и Python, которые позволяют вам писать глобальный код. Это происходит потому, что Pawn вызывается на основе другого языка: он реагирует на действия от родительского приложения и функции должны быть написаны для обработки этих действий.

В отличие от переменных функции не нужно объявлять, прежде чем использовать их. Функции имеют две части: модель и тело. Модель содержит имя вашей функции и параметры, которые она будет принимать. Тело представляет собой простой блок кода.

Пример функции:
Код: Выделить всё
AddTwoNumbers(first, second)
{
  new sum = first + second;
 
  return sum
;
}
 


Это простая функция. Модель этой строки:
Код: Выделить всё
AddTwoNumbers(first, second) 


Разберем по отдельности:
AddTwoNumbers - название функции.
first - название первого параметра, который представляет собой простой элемент.
second - название второго параметра, который представляет собой простой элемент.

В теле создается новая переменная sum и присваивается ей значение этих двух параметров, добавленных совместно (другие выражения будут позже). Важно заметить, что оператор return обозначает конец функции и возврат с полученными значениями из этой функции. Все функции возвращают значения после завершения. Это означает, например:
Код: Выделить всё
new sum = AddTwoNumbers(4, 5); 

Приведенный выше код будет присваивать число 9 к sum. Функция получает два значения и передает новое значение sum в качестве возвращаемого значения. Если функция не имеет возвращаемого значения или не имеет значений для возврата, то возвращается 0 по умолчанию.

Функция может принимать любые типы значений. Она может вернуть любую однострочную переменную, но не массивы или строки. Пример:
Код: Выделить всё
Float:AddTwoFloats(Float:a, Float:b)
{
   new Float:sum = a + b;
 
   return sum
;
}
 

Заметим, что если в приведенной выше функции вам вернулось не Float значение, то вы получите не соответствие значений.

Можно, конечно, передавать переменные в функции:
Код: Выделить всё
new numbers[3] = {1, 2, 0};
 
numbers
[2] = AddTwoNumbers(numbers[0], numbers[1]); 


Заметим, что однострочные переменные передаются по значению. То есть их значение не может быть изменено функцией. Например:
Код: Выделить всё
new a = 5;
 
ChangeValue
(a);
 
ChangeValue
(b)
{
   b = 5;
}
 

Этот код не будет менять значение a. Это происходит потому, что копия этого значения передается в b вместо a самостоятельно.

public
Публичные функции используются для осуществления обратных вызовов. Вы не должны создавать какую-либо публичную функцию, если это вынудит выполнение обратного вызова. Например, вот два обратных вызова из sourcemod.inc:
Код: Выделить всё
forward OnPluginStart();
forward OnClientDisconnected(client); 


Чтобы выполнить и получить эти два события, вы должны написать такие функции как:
Код: Выделить всё
public OnPluginStart()
{
   /* Здесь код */
}
 
public OnClientDisconnected
(client)
{
   /* Здесь код */
} 


Ключевое слово public делает функцию публичной, а также позволяет родительскому приложению непосредственно вызывать функцию.

native
Native имеют встроенные функции, предоставляемые приложением. Вы можете вызвать их, как если бы они были normal функциями. Например, SourceMod имеет следующие функции:
Код: Выделить всё
native FloatRound(Float:num); 


Её можно вызвать таким образом:
Код: Выделить всё
new num = FloatRound(5.2);     // Результат в num = 5    


Параметры массива
Вы можете передавать массивы или строки в качестве параметров. Важно отметить, что они идут как ссылка, то есть копия данных не делается, а отдается непосредственно ссылка на данные. Существует простой способ объяснить это более конкретно.
Код: Выделить всё
new example[] = {1, 2, 3, 4, 5};
 
ChangeArray
(example, 2, 29);
 
ChangeArray
(array[], index, value)
{
   array[index] = value;
}
 

Эта функция устанавливает заданный индекс в массиве с учетом значения. Когда она запускается на примере нашего массива, она меняет индекс 2 для значения 3 на 29. То есть:
Код: Выделить всё
example[2] = 29; 

Это возможно лишь потому, что массив может быть непосредственно изменён. Чтобы предотвратить массив от изменения, можно пометить его как постоянную const. Это позволит понизить риск на ошибку в коде от её изменения. Например:
Код: Выделить всё
CantChangeArray(const array[], index, value)
{
   array[index] = value;    // Не компилируется
} 

Используя const в параметрах массивов, вы будете точно знать, что он не будет изменен.

Выражения
Выражения являются точно такими же, какими они существуют в математике. Это группы операторов/символов, которые приходятся на один фрагмент данных. Они часто заключены в скобках (внутри скобок). Они содержат строгий "порядок операций". Они могут содержать переменные, функции, цифры и выражения сами могут быть вложенные внутрь других выражений, и даже приняты в качестве параметров.

Приведем пример простейшего выражения:
Код: Выделить всё
0;   // Возвращает число 0
(0); // Также возвращает число 0    

Хотя выражения могут возвращать значения, они также могут ответить какое значение содержит ответ ноль или не ноль. В этом смысле, ноль является ложью (false), а не нулевое значение истиной (true). Например, -1 истина в Pawn, поскольку она не является нулем. Не думайте, что отрицательные числа являются ложными.

Порядок операций выражения аналогичен языку C. Вот несколько примеров выражений:
Код: Выделить всё
+ 6;                   // Вычисляет как 11
* 6 + 3;               // Вычисляет как 33
* (+ 3);             // Вычисляет как 45
5.0 + 2.3;               // Вычисляет как 7.3
(* 6) % 7;             // Модульный оператор, вычисляет как 2
(+ 3) / 2 * 4 - 9;     // Вычисляет как 7    


Как уже отмечалось, выражения могут содержать переменные, или даже функции:
Код: Выделить всё
new a = 5 * 6;
new b = a * 3;      // Вычисляет как 90
new c = AddTwoNumbers(a, b) + (* b); 


Операторы
Есть несколько полезных дополнительных операторов в Pawn. Например:
Код: Выделить всё
new a = 5;
 
= a + 5; 


Может быть переписан, как:
Код: Выделить всё
new a = 5;
+= 5; 


Это верно в отношении следующих операторов в Pawn:
*, /, -, +
|, &, ^, ~, <<, >>


Кроме того, существуют операторы увеличение/понижения:
Код: Выделить всё
= a + 1;
= a - 1; 


Может быть упрощено, как:
Код: Выделить всё
a++;
a--; 


Дополнительно отметим, что ++ или -- может быть представлен до переменной (до-инкремент, до-декремент) или после переменной (пост-инкремент, пост-декремент). Разница заключается в том, как остальная часть выражения видит результат.
До: переменная увеличивается до определения и остальная часть выражения видит новое значение.
Пост: переменная увеличивается после определения и остальная часть выражения видит старое значение.

Например:
Код: Выделить всё
new a = 5;
new b = a++;   // b = 5, a = 6
new c = ++a;   // a = 7, c = 7    


Операторы сравнения
Существуют шесть операторов для сравнения двух числовых значений, а результат является либо истиной (не ноль), либо ложью (ноль):
  • a == b - действительно, если a и b имеют одинаковые значения.
  • a != b - действительно, если b имеет другое значение.
  • a > b - действительно, если a больше b
  • a >= b - действительно, если a больше или равно b
  • a < b - действительно, если a меньше b
  • a <= b - действительно, если a меньше или равно b

Например:
Код: Выделить всё
(!= 3);         // Определяется как истина, поскольку 1 не равно 3
(+ 3 == 6);     // Определяется как истина, поскольку 3+3 равно 6
(- 2 >= 4);     // Определяется как ложь, поскольку 3 меньше 4    


Заметим, что эти операторы не работают с массивами и строками. То есть, вы не можете сравнить их с помощью ==.

Действительные операторы
Действительные операторы могут быть скомбинированы тремя булевыми (boolean) операторами:
  • a && b - истина, если a и b истинные. Ложь, если a и (или) b ложные.
  • a || b - истина, если a или b (или обе переменные) истинные. Ложь, если обе переменные a и b ложные.
  • !a - Истина, если a ложь. Ложь, если a истина.


Например:
Код: Выделить всё
(|| 0);         // Определяется как истина, так как выражение 1 истинное
(&& 0);         // Определяется как ложь, так как выражение 0 ложное
(!|| 0);        // Определяется как ложь, так как выражение !1 ложное    


Левое/правое значения
Два важных понятия левого и правого значений или левостороннее и правостороннее значения. Левостороннее значение имеет то, что появляется на левой стороне выражения, а правостороннее значение - на правой стороне выражения.

Например:
Код: Выделить всё
new a = 5; 


В этом примере a является левосторонним значением и 5 является правосторонним значением.

Правила:
  • Выражения никогда не будут левосторонними значениями.
  • Переменные являются двумя: левосторонними и правосторонними значениями.

Условия
Условия позволяют вам запускать код, если определенное условие выполнено.

Если соответствует (if/else if)
Если соответствует одно или более условий. Например:
Код: Выделить всё
if (== 5)
{
   /* Код будет запущен, если условие будет истинным */
} 


Они могут быть расширены для более сложных случаев:
Код: Выделить всё
if (== 5)
{
   /* Код */
}
else if (== 6)
{
   /* Код */
}
else if (== 7)
{
   /* Код */
} 


Вы также можете обрабатывать случаи, даже если выражение не верно. Например:
Код: Выделить всё
if (== 5)
{
   /* Код */
}
else
{
   /* Код, который будет запущен если нет истинного выражения */
} 


Оператор выбора (switch)
Оператор выбора будет ограничен условием. Он необходим для выражения, выполняющего код для целого ряда возможных значений. Например:
Код: Выделить всё
switch (a)
{
   case 5:
   {
      /* Код, который будет выполняться, если a равно 5 */
   }
   case 6:
   {
      /* Код, который будет выполняться, если a равно 6 */
   }
   case 7:
   {
      /* Код, который будет выполняться, если a равно 7 */
   }
   case 8, 9, 10:
   {
      /* Код, который будет выполняться, если a равно или 5, или 9, или 10 */
   }
   default:
   {
      /* Код, который будет выполняться, если ни одно условие не соответствует */
   }
}
 


Циклы
Циклы позволяют вам без труда повторять выполнение кода, пока условие истинно.

For циклы
For циклы - это циклы, которые состоят из четырех частей:
  • Оператор инициализации - запускается один раз перед первым циклом.
  • Оператор условия - проверяет условие и запускает следующий цикл, в том числе первый. Цикл прекращается, когда это выражение становится ложным.
  • Оператор итерации - запускается после каждого цикла.
  • Тело цикла - запускается каждый раз, пока оператор условия вычисляется как истинный.

Код: Выделить всё
for ( /* инициализация */ ; /* условие */ ; /* итерация */ )
{
   /* тело */
} 


Простым примером является функция сложения массива:
Код: Выделить всё
new array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
new sum = SumArray(array, 10);
 
SumArray
(const array[], count)
{
   new total;
 
   for 
(new i = 0; i < count; i++)
   {
      total += array[i];
   }
 
   return total
;
}
 


Разберем подробнее:
new i = 0 - создает новую переменную для цикла i и устанавливает её в 0.
i < count - только запускает цикл, если i меньше, чем count. Это гарантирует, что чтение цикла прекращается в определенный момент. В этом случае, мы не хотим читать недействительные индексы в массиве.
i++ - увеличивает i на единицу после каждого цикла. Это гарантирует, что цикл не будет запущен вечно. В конце концов i станет слишком большим и цикл завершится.

Таким образом, функция SumArray будет циклом каждого действительного индекса массива, каждый раз добавляя это значение в sum.

While циклы
While циклы являются менее распространенными, чем for циклы, но на самом деле это более простые циклы. Они имеют только две части:
  • Оператор условия - проверяется перед началом каждого цикла. Цикл прекращается, когда условие становится ложным.
  • Тело цикла - запускается каждый раз пока цикл выполняется.

Код: Выделить всё
while ( /* условие */ )
{
   /* тело */
} 


До тех пор, пока условие выражения остается истинным, цикл будет выполняться. Каждый for цикл может быть переписан, как while цикл:
Код: Выделить всё
/* инициализация */
while ( /* условие */ )
{
   /* тело */
   /* итерация */
} 


Пример предыдущего for цикла, переписанного, как while цикл:
Код: Выделить всё
SumArray(const array[], count)
{
   new total, i;
 
   while 
(< count)
   {
      total += array[i];
      i++;
   }
 
   return total
;
}
 


Существуют также do...while циклы, которые используются еще реже. Они работают как и while циклы, но проверяют условие после каждого цикла, а не перед ним. Это означает, что цикл всегда будет запущен, по крайней мере один раз. Например:
Код: Выделить всё
do
{
   /* тело */
}
while ( /* условие */ ); 


Управление циклами
Существуют два случая, когда нам нужно контролировать цикл:
  • пропустить одну итерацию или цикл и продолжить выполнение цикла, как обычно;
  • разорвать цикл целиком, прежде чем он закончится.

Допустим у вас есть функция, которая принимает массив и ищет соответствия цифр. Вы хотите его остановить, когда число будет найдено:
Код: Выделить всё
/**
 * Возвращает массив, если индекс значения или -1, или не найден.
 */
SearchInArray(const array[], count, value)
{
   new index = -1;
 
   for 
(new i = 0; i < count; i++)
   {
      if (array[i] == value)
      {
         index = i;
         break;
      }
   }
 
   return index
;
}
 

Конечно, эту функцию можно вернуть и способом return i, но пример показывает, как break может остановить цикл.

Кроме того, ключевое слово continue пропускает итерации цикла. Например, мы хотим суммировать все четные числа:
Код: Выделить всё
SumEvenNumbers(const array[], count)
{
   new sum;
 
   for 
(new i = 0; i < count; i++)
   {
      if (array[i] % 2 == 1)
      {
         /* Пропускаем оставшуюся часть итерации цикла */
         continue;
      }
      sum += array[i];
   }
 
   return sum
;
}
 


Область действия
Область действия относится к удобочитаемости кода. Это означает, что код одного уровня не может быть "виден" в коде другого уровня. Пример:
Код: Выделить всё
new A, B, C;
 
Function1
()
{
   new B;
 
   Function2
();
}
 
Function2
()
{
   new C;
}
 


В этом примере A, B и C имеют глобальную область действия. Их можно увидеть в любой функции. Вместе с тем, B в функции Function1 не является той же переменной, как B на глобальном уровне. Вместо этого она находится в локальной области действия, и поэтому является локально изменяемой. Кроме того, функции Function1 и Function2 ничего не знают о существовании других переменных. Она так же является не только локальной переменной функции Function1, но и создается заново каждый раз, когда функция вызывается. Попробуйте представить это:
Код: Выделить всё
Function1()
{
   new B;
 
   Function1
();
}
 

В приведенном выше примере, функция Function1 вызывает сама себя. Конечно, это бесконечная рекурсия, что очень плохо, но идея заключается в том, что каждый раз, когда функция срабатывает, то создается новая копия B. Когда функция завершается, B уничтожается и её значение теряется.

Это свойство можно объяснить проще тем, что область действия переменной равна уровню её вложенности. То есть переменная на глобальной области действия видна для всех функций. Переменная в локальной области действия видна всему блоку кода расположенному "ниже" этого уровня. Например:
Код: Выделить всё
Function1()
{
   new A;
 
   if 
(A)
   {
      A = 5;
   }
}
 

Приведенный выше код является действительным, поскольку область действия распространяется по всей функции. Однако этот код, работать не будет:
Код: Выделить всё
Function1()
{
   new A;
 
   if 
(A)
   {
      new B = 5;
   }
 
   B 
= 5;
}
 

Отметим, что B объявляется в новом блоке кода. Это означает, что B доступна только в том блоке кода, в котором была создана (и всем под-блокам вложенных внутри него). Как только блок кода прекращается, B становится не действительной.

Динамические массивы
Динамические массивы - это массивы, которые не имеют фиксированного размера. Например:
Код: Выделить всё
Function1(size)
{
   new array[size];
 
   
/* Код */
}
 


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

Динамические массивы действительны только в локальной области действия, так как код не может существовать на глобальном уровне.

Расширенное объявление переменных
Переменные могут быть объявлены другим путем, чем просто new.

decl
По умолчанию, все переменные в Pawn будут инициализированы, как нуль. Если есть явная инициализация, переменная инициализируется для выражения = определенным символам. В локальной области действия, это может потребовать время на выполнение. Ключевое слово decl (которое действительно только в локальной области действия) было введено, чтобы позволить решать пользователю: хочет ли он инициализировать переменную или нет.

Примечание: decl не должно быть использовано на одну однострочную переменную. Это будет не выгодно.

Например:
Код: Выделить всё
new c = 5;
new d;
new String:blah[512];
 
Format
(blah, sizeof(blah), "%d %d", c, d); 


В этом коде c равна 5 и d равна 0. Во время выполнения этого кода затраты на инициализацию незначительные. Вместе с тем, blah является большим массивом и затраты на инициализацию всего массива могут быть больше 0 сек. и иметь плохие последствия в определенных ситуациях.

Заметим, что blah не должен быть нулевой. В период времени с объявления new и и перемещения в Format(), массив blah никогда не будет загружен или прочитан. Данный код будет более эффективен, если будет написанный следующим образом:
Код: Выделить всё
new c = 5;
new d;
decl String:blah[512];
 
Format
(blah, sizeof(blah), "%d %d", c, d); 


Предостережения: Обратная сторона decl состоит в том, что его переменные будут начинаться с "ненужного" содержания. Например, если мы будем использовать:
Код: Выделить всё
new c = 5;
new d;
decl String:blah[512];
 
PrintToServer
("%s", blah);
 
Format
(blah, sizeof(blah), "%d %d", c, d); 


Этот код может привести к падению сервера, так как массив blah может быть полностью испорчен (строки требуют нулевой символ, который может отсутствовать). Точно так же, если мы сделаем:
Код: Выделить всё
new c = 5;
decl d;
decl String:blah[512];
 
Format
(blah, sizeof(blah), "%d %d", c, d); 

Значение d в настоящее время не определено. Оно может быть любым значением, отрицательным или положительным.
Заметим, что это легко и эффективно обезопасит строки. Пример ниже показывает, как предотвратить строки от мусора:
Код: Выделить всё
decl String:blah[512];
 
blah
[0] = '\0'; 


Золотые правила:
  • Используйте decl только, если в период объявления и загрузки/чтения значения, вы абсолютно уверены, что есть по крайней мере одно хранилище/операция, которая отдает переменной действительные данные.
  • Не оптимизируйте преждевременно. Кроме того, нет необходимости использовать decl на не массивы, поскольку нет никаких дополнительных затрат на инициализацию одного однострочного значения.

Этот пример не является столь эффективным, как decl:
Код: Выделить всё
new String:blah[512] = "a"; 

Даже несмотря на то, что строка имеет только один символ, оператор new гарантирует, что остальная часть массива будет нулевой.
Также обратите внимание, что он является неправильным для явной инициализации decl:
Код: Выделить всё
decl String:blah[512] = "a"; 

Приведенный выше код не будет компилироваться, потому что цель decl состоит в том, чтобы избежать каких-либо инициализаций.

static
Ключевое слово static входит в глобальную и локальную область действия. Оно имеет различные значения в каждой из них.

Глобальный static
Глобальные static переменные могут быть доступны только в рамках того же файла.
Например:
Код: Выделить всё
// file1.inc
static Float:g_value1 = 0.15f;
 
// file2.inc
static Float:g_value2 = 0.15f; 


Если плагин включает в себя оба этих файла, он не сможет использовать g_value1 или g_value2. Это простой механизм сокрытия информации и аналогичен элементам объявления переменных, например, private в таких языках, как C++, Java или C#.

Локальный static
Локальная static переменная является видимой лишь из её местной области действия.
Например:
Код: Выделить всё
MyFunction(inc)
{
   static counter = -1;
 
   counter 
+= inc;
 
   return counter
;
}
 

В этом примере counter технически глобальная переменная - она инициализируется один раз как -1 и никогда не инициализируется заново. Это означает, что при каждом запуске функции MyFunction, переменная counter и ее хранение в памяти одно и тоже.

Возьмем следующий пример:
Код: Выделить всё
MyFunction(5);
MyFunction(6);
MyFunction(10); 

В этом примере, counter может быть -1 +5 +6 +10 или 20, поскольку она сохраняется за рамками этой функции. Это может создавать проблемы для рекурсивной функции: если ваша функция может быть рекурсивной, то static, как правило, не является хорошей идеей, если только ваш код не реентерабельный.

Преимуществом локальных static переменных является то, что вам не придется загромождать свой сценарий глобальными переменными. До тех пор, пока переменной не нужно читать другую функцию, Вы можете его пихать внутрь функции и её сохранение будет гарантировано.

Заметим, что static может существовать в любой локальной области действия:
Код: Выделить всё
MyFunction(inc)
{
   if (inc > 0)
   {
      static counter;
      return (counter += inc);
   }
   return -1;
}
 
Не пишите мне в ЛС: если вам нужна помощь на бесплатной основе. Любые вопросы на форум.
Аватара пользователя
DJ_WEST
Администратор
 
Сообщения: 3641
Зарегистрирован: 22 авг 2009, 00:38
Благодарил (а): 48 раз.
Поблагодарили: 2209 раз.
Опыт программирования: Больше трех лет
Языки программирования: Counter-Strike 1.6
Counter-Strike: Source
Left 4 Dead
Left 4 Dead 2

Re: Введение в SourcePawn программирование

Сообщение StRiKeR.csF » 06 дек 2011, 10:02

Как я понял, он от amx фактически не отличается?
Чего добился ты и бла, бла, бла
Тут, типа, посыл был, но админ его потёр :(

Skype: nestle.csf
-В чём сила, брат?
-Сила в Debug.log xD
Аватара пользователя
StRiKeR.csF
Скриптер
 
Сообщения: 771
Зарегистрирован: 03 июн 2011, 06:26
Откуда: Кубань
Благодарил (а): 126 раз.
Поблагодарили: 181 раз.
Опыт программирования: Больше трех лет
Языки программирования: C++
C#
JavaScript
PHP(+MySQL)
Assembler

Re: Введение в SourcePawn программирование

Сообщение DJ_WEST » 13 дек 2011, 15:43

Отличается - синтаксисом, названиями функций и т.д. У SM больше встроенных возможностей и Source движок интересней.
Не пишите мне в ЛС: если вам нужна помощь на бесплатной основе. Любые вопросы на форум.
Аватара пользователя
DJ_WEST
Администратор
 
Сообщения: 3641
Зарегистрирован: 22 авг 2009, 00:38
Благодарил (а): 48 раз.
Поблагодарили: 2209 раз.
Опыт программирования: Больше трех лет
Языки программирования: Counter-Strike 1.6
Counter-Strike: Source
Left 4 Dead
Left 4 Dead 2

Re: Введение в SourcePawn программирование

Сообщение Fedcomp » 13 дек 2011, 18:01

У Source движка сложнее работа со светом, и не забывайте что там есть физика.
Не помогаю в ЛС - есть форум.
Плагины тоже не пишу, на форуме достаточно хороших скриптеров.


"я ставлю зависимости потому что мне приятно" - subb98 @ 2017
Аватара пользователя
Fedcomp
Администратор
 
Сообщения: 4936
Зарегистрирован: 28 авг 2009, 20:47
Благодарил (а): 813 раз.
Поблагодарили: 1317 раз.
Языки программирования: =>
pawn / php / python / ruby
javascript / rust

Re: Введение в SourcePawn программирование

Сообщение StRiKeR.csF » 14 дек 2011, 02:51

Ну я и писал, фактически...
Чего добился ты и бла, бла, бла
Тут, типа, посыл был, но админ его потёр :(

Skype: nestle.csf
-В чём сила, брат?
-Сила в Debug.log xD
Аватара пользователя
StRiKeR.csF
Скриптер
 
Сообщения: 771
Зарегистрирован: 03 июн 2011, 06:26
Откуда: Кубань
Благодарил (а): 126 раз.
Поблагодарили: 181 раз.
Опыт программирования: Больше трех лет
Языки программирования: C++
C#
JavaScript
PHP(+MySQL)
Assembler

Re: Введение в SourcePawn программирование

Сообщение DJ_WEST » 22 дек 2011, 11:20

У Source движка сложнее работа со светом, и не забывайте что там есть физика.

Чем сложнее, тем интереснее :)
Не пишите мне в ЛС: если вам нужна помощь на бесплатной основе. Любые вопросы на форум.
Аватара пользователя
DJ_WEST
Администратор
 
Сообщения: 3641
Зарегистрирован: 22 авг 2009, 00:38
Благодарил (а): 48 раз.
Поблагодарили: 2209 раз.
Опыт программирования: Больше трех лет
Языки программирования: Counter-Strike 1.6
Counter-Strike: Source
Left 4 Dead
Left 4 Dead 2

Re: Введение в SourcePawn программирование

Сообщение Fedcomp » 22 дек 2011, 12:10

ну, там можно получить значительно более интересные эффекты от света/физики. Т.е площадка под идеи для новых модов
Не помогаю в ЛС - есть форум.
Плагины тоже не пишу, на форуме достаточно хороших скриптеров.


"я ставлю зависимости потому что мне приятно" - subb98 @ 2017
Аватара пользователя
Fedcomp
Администратор
 
Сообщения: 4936
Зарегистрирован: 28 авг 2009, 20:47
Благодарил (а): 813 раз.
Поблагодарили: 1317 раз.
Языки программирования: =>
pawn / php / python / ruby
javascript / rust

Re: Введение в SourcePawn программирование

Сообщение burnx » 19 авг 2012, 14:13

У меня вопрос по теме, я объявляю массив строк внутри функции, выполняется код форматирующий этот массив, после последующих вызовов этой функции массив не создается заново, а используется с прошлых вызовов, а мне нужно чтобы массив был пустым как при его создании, почему так? и как "обнулить" его?
Аватара пользователя
burnx
 
Сообщения: 81
Зарегистрирован: 28 окт 2011, 21:40
Благодарил (а): 19 раз.
Поблагодарили: 9 раз.
Опыт программирования: Меньше месяца
Языки программирования: Counter-Strike 1.6

Re: Введение в SourcePawn программирование

Сообщение Retro-kolt Lincoln » 11 сен 2014, 01:17

Вроде нечего сложного, CS:GO набирает популярность думаю нужно попробовать писать свои плагины...
Предлагаю услуги гаранта. Написание плагинов на заказ.
Статус:
на заслуженном отдыхе
Отзывы: Нажми
Обратиться ко мне: Нажми

- - - - - - - - - - - - - - - -
Если ваше ЛС было проигнорировано мною, знайте, оно мне не интересно.
Аватара пользователя
Retro-kolt Lincoln
 
Сообщения: 1283
Зарегистрирован: 28 авг 2010, 19:16
Благодарил (а): 321 раз.
Поблагодарили: 581 раз.
Опыт программирования: Больше трех лет
Языки программирования: ╚►Counter-Strike 1.6

Пред.

Вернуться в Статьи / фрагменты кода

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3