Ru:Introduction to SourcePawn
Это руководство призвано дать Вам самые основные представления по основам написания сприптов в SourcePawn. Pawn это "скриптовый" язык используемый для внедрения функциональности в других программах. Это означает, что это не самостоятельный язык, как C++ или Java, и его элементы будут отличаться в различных приложениях. SourcePawn это вариация языка Pawn используемая в SourceMod.
Это руководство не расскажет Вам как писать SourceMod плагины; оно предназначено для получения общих представлений о синтаксисе и семантике этого языка. Читайте отдельную статью Ru:Introduction to SourceMod Plugins (Введение в SourceMod плагины), для введения в SourceMod API.
Contents
Введение для новичков
Этот раздел создан не для программистов. Если Вы по прежнему в замешательстве, Вы можете прочитать книги о других языках программирования, таких как PHP, Python, или Java, чтобы получить более полное представление о программировании.
Идентификаторы/Ключевые слова
Идентификаторы представляет собой набор букв, цифр и/или нижнего подчеркивания, что представляет собой нечто уникальное. Идентификаторы вводятся с учетом регистра (в отличие от PHP, где иногда это не требуется). Идентификаторы не начинаются с какого-либо специального символа, но они должны начинаться с буквы.
Есть несколько зарезервированных символов, которые имеют особое значение. Например, if, for, и return специальные конструкции в языке, которые будут описаны позднее. Они не могут быть использованы в качестве названий идентификаторов.
Переменные
Существует несколько важных конструкций, которые Вы должны знать, прежде чем приступить к написанию сценария. Во-первых, это переменные. Переменная это идентификатор, который содержит данные. Например, переменная "a" может содержать числа "2", "16", "0", и так далее. Переменные создаются для хранения данных внутри программы. Переменные должны быть объявлены до их использования, с помощью ключевого слова "new". Данные можно присвоить переменной, используя знак равенства (=). Пример:
new a, b, c, d; a = 5; b = 16; c = 0; d = 500;
В SourcePawn, переменные бывают двух типов, которые будут более подробно описаны далее.
- Однострочные (могут содержать только произвольные числовые данные), как показано выше.
- Многострочные (могут содержать целый ряд текстовых символов)
Функции
Следующим важным понятием являются функции. Функции идентификаторов или имен, которые выполняют действия. Это означает, что когда вы их активируете, они выполняют конкретную последовательность кода. Есть несколько типов функций, но все функции активируется одинаковым образом. "Вызов функции" является термином ссылающимся на функцию действия. Функция числовых переменных строятся так:
функция(<параметры>)
Примеры:
show(56); //Активирует функцию "show" и присваивает ей число 56 show(); //Активирует функцию "show" без каких-либо данных, пустую show(a); //Активирует функцию "show" и присваивает ей переменную с данными
Каждый фрагмент данных передаваемый вызываемой функции, называется параметр. Функция может иметь любое количество параметров (но есть "допустимый" предел в SourceMod: 32). Параметры будут описаны далее в этой статье.
Комментарии
Примечания и любой текст, который пишется после "//" считается "Комментарием", а не фактическим кодом. Есть два стиля комментариев:
- // - Двойная косая черта, всё следующие после этой строки игнорируется.
- /* */ - Много-строчный комментарий, весь текст, внутри звездочек игнорируются. You cannot nest these.
Массивы
Описание массивов. Вы можете группировать код в виде "массивов", разделенных { и }. Это фактически создает возможность работать с целым массивом как с одним оператором. Например:
{ here; is; some; code; }
Массивы с фигурными скобками используются достаточно широко в программировании. Массивы кода могут быть вложенными друг в друга. Это хорошая возможность адаптировать последовательность когда и сделать его удобочитаемым, благодаря отступам код не будет смотреться как одна большая и длинная макаронина.
Особенности языка
Pawn может показаться очень похожим на другие языки программирования, например C, но Pawn от них фундаментально отличается. Не столь важно, чтобы Вы сейчас же поняли его отличия, но они понадобятся, если Вы уже знаете один из языков программирования.
- Pawn не печатает Pawn имеет только один тип данных - однострочный. Подробнее будет описано позже. [В дальнейшем автор рассказывает, что существует два типа данных: однострочный и многострочный]
- Pawn не собирает мусор Pawn, как язык, не имеет встроенных ресурсов памяти, и потому он не мусорит. Если функция выделит память, то Вы отвечаете за её освобождение.
- Pawn не объектно-ориентированный язык Pawn является процедурным, и полагается на подпрограммы. Также у него нету C подобных структур.
- Pawn не функциональный. Pawn является процедурным, и не поддерживает функции "лямбды" (Lambda), поздние присвоения, и все то, что можно найти в языках высшего уровня, таких как Phyton и Ruby.
- Pawn однопоточный As of this writing, Pawn is not thread safe.
- Pawn не интерпретируемый Ну, почти. Он интерпретируется на очень низком уровне. Вы должны скомпилировать код, из которого получится бинарный файл. Эта программа будет работать на той платформе, которую использует хост. Это ускоряет загрузку и позволяет легче находить ошибки.
Этот язык был выпущен ITB CompuPhase. Язык разработан для устройств низкого уровня и таким образом конечные программы очень маленькие по размеру и очень быстрые.
Переменные
В Pawn есть всего два типа переменных: однострочные и многострочные. Однострочные могут содержать 32 бита цифровых данных. Многострочные - последовательный список из UTF-8 символов.
однострочные не имеет своего типа, однако они могут быть маркированы(tagged). Тег позволяет Вам указывать, где определенную ячейку можно использовать. Типичные теги:
- (пусто), или _ - Нет тега. Обычно используют для целых чисел (Integers).
- Float - используют для чисел с плавающей точкой (небольших).
- bool - используют для хранения значений true (истина) или false (ложь).
Со строками все по другому, они будут рассмотрены далее.
Объявления
Примеры разных правильных объявлений переменных.
new a = 5; new Float:b = 5.0; new bool:c = true; new bool:d = 0; //Работает, поскольку 0 равно false (ложь)
Неправильные объявления переменных
new a = 5.0; //Несоответствие тегов. 5.0 с тегом Float new Float:b = 5; //Несоответствие тегов. 5 без тега.
Если переменная не определена в объявлении то ее значения станет 0
new a; //значение 0 new Float:b; //значение 0.0 new bool:c; //значение false
Присвоение
Переменным могут быть присвоены данные после создания. Пример:
new a, Float:b, bool:c; a = 5; b = 5.0; c = true;
Массивы
Массив это последовательность данных в последовательном списке. Массивы очень полезны для хранения нескольких единиц данных в одной переменной, а зачастую могут значительно упростить многие задачи.
Описание
Массив объявляется с помощью квадратных скобок. Вот некоторые примеры массивов:
new players[32]; //Набор из 32 однострочных (числовых) данных new Float:origin[3]; //Набор из 3 чисел с плавающей точкой
По умолчанию, массивам присваиваются нули. Вы можете присвоить им разные значения по умолчанию, однако:
new numbers[5] = {1, 2, 3, 4, 5}; //Набор 1, 2, 3, 4, 5 из однострочных данных. new Float:origin[3] = {1.0, 2.0, 3.0}; //Набор 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]; a = 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}; //Верно, но это НЕ СТРОКА.
Функции
Функции, как отмечалось ранее, имеют отдельные составляющие кода, которые выполняют определенные действия. Функции могут быть задействованы или вызвоны с параметрами, которые дают особые настройки.
Существуют два типа вызова функции:
- прямой вызов - Вы специально вызываете функцию в своем коде.
- обратный вызов - Применение вызова функций в Вашем коде, как если бы это было событием триггера (совокупность условий, инициирующих выполнение действия).
Существуют пять видов функций:
- 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. Это происходит потому, что копия этого значения в a передается вместо a самостоятельно.
Больше примеров функций будут демонстрироваться и в других частях статьи.
Publics
Публичные функции используются для осуществления обратных вызовов. Вы не должны создавать какую-либо публичную функцию, если это вынудит выполнение обратного вызова. Например, вот два обратных вызова из sourcemod.inc:
forward OnPluginStart(); forward OnClientDisconnected(client);
Чтобы выполнить и получить эти два события, Вы должны написать такие функции как:
public OnPluginStart() { /* Код здесь */ } public OnClientDisconnected(client) { /* Код здесь */ }
Ключевое слово public делает функцию публичной, а также позволяет родительскому приложению непосредственно вызывать функцию.
Natives
Natives имеют встроенные функции, предоставляемые приложением. Вы можете вызвать их, как если бы они были 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. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Вот несколько примеров выражений:
5 + 6; //Вычисляет как 11 5 * 6 + 3; //Вычисляет как 33 5 * (6 + 3); //Вычисляет как 45 5.0 + 2.3; //Вычисляет как 7.3 (5 * 6) % 7; //Modulo operator, вычисляет как 2 (5 + 3) / 2 * 4 - 9; //Вычисляет как 7
Как уже отмечалось, выражения могут содержать переменные, или даже функции:
new a = 5 * 6; new b = a * 3; //Вычисляет как 90 new c = AddTwoNumbers(a, b) + (a * b);
Операторы
Есть несколько полезных дополнительных операторов в Pawn. Первый набор упрощает аутоагрегацию выражения. Например:
new a = 5; a = a + 5;
Может быть переписан, как:
new a = 5; a += 5;
Это верно в отношении следующих операторов в Pawn:
- Four-function: *, /, -, +
- Bit-wise: |, &, ^, ~, <<, >>
Кроме того, существуют инкремент/декремент операторы:
a = a + 1; a = a - 1;
Может быть упрощено, как:
a++; a--;
Дополнительно отметим, что ++ или -- может быть представлен до переменной (до-инкремент, до-декремент) или после переменной (пост-инкремент, пост-декремент). Разница заключается в том, как остальная часть выражения содержащие их, видит результат.
- До: Переменная увеличивается до определения и остальная часть выражения видит новое значение.
- Пост: Переменная увеличивается после определения и остальная часть выражения видит старое значение.
Иными словами, a++ определяет значение как a в то время как ++a определяет значение как a + 1. В обоих случаях a увеличивается на 1.
Например:
new a = 5; new b = a++; // b = 5, a = 6 (1) new c = ++a; // a = 7, c = 7 (2)
(1) b присваивается a со старым значением, до того, как он будет увеличено до 6. (2) c присваивается a с новым значением, после того, как он увеличивается до 7.
Операторы сравнения
Существуют шесть операторов для сравнения двух числовых значений, а результат является либо истиной (не ноль) или ложью (ноль):
- a == b - Действительно, если и b имеет то же значение.
- a != b - Действительно, если b имеет другое значение.
- a > b - Действительно, если оно больше b
- a >= b - Действительно, если оно больше или равно b
- a < b - Действительно, если оно меньше b
- a <= b - Действительно, если оно меньше или равно b
Например:
(1 != 3); //Определяется как истина, поскольку 1 не равно 3. (3 + 3 == 6); //Определяется как истина, поскольку 3+3 равно 6. (5 - 2 >= 4); //Определяется как ложь, поскольку 3 меньше 4.
Заметим, что эти операторы не работают с массивами и строками. То есть, вы не можете сравнить их с помощью ==.
Действительные операторы
Действительные операторы могут быть скомбинированы тремя булевыми (boolean) операторами:
- a && b - Истина, если a и b истинные. Ложь, если a и (или) b ложные.
&& | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
- a || b - Истина, если a или b (или обе переменные) истинные. Ложь, если обе переменные a и b ложные.
|| | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 1 |
- !a - Истина, если a ложь. Ложь, если a истина.
! | 0 | 1 |
---|---|---|
1 | 0 |
Например:
(1 || 0); //Определяется как истина, так как выражение 1 истинное (1 && 0); //Определяется как ложь, так как выражение 0 ложное (!1 || 0); //Определяется как ложь, так как выражение !1 ложное.
Левое/правое значения
Два важных понятия левого и правого значений, или левостороннее и правостороннее значения. Левостороннее значение имеет то, что появляется на левой стороне выражения, а правостороннее значение - появляется на правой стороне выражения.
Например:
new a = 5;
В этом примере a является левосторонним значением и 5 является правосторонним значением.
Правила:
- Выражения никогда не будут левосторонними значениями.
- Переменные являются двумя, левосторонними и правосторонними значениями.
Conditionals
Conditional statements let you only run code if a certain condition is matched.
If Statements
If statements test one or more conditions. For example:
if (a == 5) { /* Code that will run if the expression was true */ }
They can be extended to handle more cases as well:
if (a == 5) { /* Code */ } else if (a == 6) { /* Code */ } else if (a == 7) { /* Code */ }
You can also handle the case of no expression being matched. For example:
if (a == 5) { /* Code */ } else { /* Code that will run if no expressions were true */ }
Switch Statements
Switch statements are restricted if statements. They test one expression for a series of possible values. For example:
switch (a) { case 5: { /* code */ } case 6: { /* code */ } case 7: { /* code */ } case 8, 9, 10: { /* Code */ } default: { /* will run if no case matched */ } }
Unlike some other languages, switches are not fall-through. That is, multiple cases will never be run. When a case matches its code is executed, and the switch is then immediately terminated.
Loops
Loops allow you to conveniently repeat a block of code while a given condition remains true.
For Loops
For loops are loops which have four parts:
- The initialization statement - run once before the first loop.
- The condition statement - checks whether the next loop should run, including the first one. The loop terminates when this expression evaluates to false.
- The iteration statement - run after each loop.
- The body block - run each time the condition statement evaluates to true.
for ( /* initialization */ ; /* condition */ ; /* iteration */ ) { /* body */ }
A simple example is a function to sum an array:
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; }
Broken down:
- new i = 0 - Creates a new variable for the loop, sets it to 0.
- i < count - Only runs the loop if i is less than count. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.
- i++ - Increments i by one after each loop. This ensures that the loop doesn't run forever; eventually i will become too big and the loop will end.
Thus, the SumArray function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.
While Loops
While loops are less common than for loops but are actually the simplest possible loop. They have only two parts:
- The condition statement - checked before each loop. The loop terminates when it evaluates to false.
- The body block - run each time through the loop.
while ( /* condition */ ) { /* body */ }
As long as the condition expression remains true, the loop will continue. Every for look can be rewritten as a while loop:
/* initialization */ while ( /* condition */ ) { /* body */ /* iteration */ }
Here is the previous for loop rewritten as a while loop:
SumArray(const array[], count) { new total, i; while (i < count) { total += array[i]; i++; } return total; }
There are also do...while loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:
do { /* body */ } while ( /* condition */ );
Loop Control
There are two cases in which you want to selectively control a loop:
- skipping one iteration of the loop but continuing as normal, or;
- breaking the loop entirely before it's finished.
Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:
/** * Returns the array index where the value is, or -1 if not found. */ SearchInArray(const array[], count, value) { new index = -1; for (new i = 0; i < count; i++) { if (array[i] == value) { index = i; break; } } return index; }
Certainly, this function could simply return i instead, but the example shows how break will terminate the loop.
Similarly, the continue keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:
SumEvenNumbers(const array[], count) { new sum; for (new i = 0; i < count; i++) { /* If divisibility by 2 is 1, we know it's odd */ if (array[i] % 2 == 1) { /* Skip the rest of this loop iteration */ continue; } sum += array[i]; } return sum; }
Scope
Scope refers to the visibility of code. That is, code at one level may not be "visible" to code at another level. For example:
new A, B, C; Function1() { new B; Function2(); } Function2() { new C; }
In this example, A, B, and C exist at global scope. They can be seen by any function. However, the B in Function1 is not the same variable as the B at the global level. Instead, it is at local scope, and is thus a local variable.
Similarly, Function1 and Function2 know nothing about each other's variables.
Not only is the variable private to Function1, but it is re-created each time the function is invoked. Imagine this:
Function1() { new B; Function1(); }
In the above example, Function1 calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of B. When the function ends, B is destroyed, and the value is lost.
This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:
Function1() { new A; if (A) { A = 5; } }
The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:
Function1() { new A; if (A) { new B = 5; } B = 5; }
Notice that B is declared in a new code block. That means B is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, B is no longer valid.
Dynamic Arrays
Dynamic arrays are arrays which don't have a hardcoded size. For example:
Function1(size) { new array[size]; /* Code */ }
Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.
Extended Variable Declarations
Variables can be declared in more ways than simply new.
decl
Purpose
By default, all variables in Pawn are initialized to zero. If there is an explicit initializer, the variable is initialized to the expression after the = token. At a local scope, this can be a run-time expense. The decl keyword (which is only valid at local scope) was introduced to let users decide if they want variables initialized or not.
Note: decl should not be used on single cell variables. There is almost never any benefit.
Explanation
For example:
new c = 5; new d; new String:blah[512]; Format(blah, sizeof(blah), "%d %d", c, d);
In this code, c is equal to 5 and d is equal to 0. The run-time expense of this initialization is negligible. However, blah is a large array, and the expense of initializing the entire array to 0s could be detrimental in certain situations.
Note that blah does not need to be zeroed. In between being declared with new and stored with Format(), blah is never loaded or read. Thus this code would be more efficiently written as:
new c = 5; new d; decl String:blah[512]; Format(blah, sizeof(blah), "%d %d", c, d);
Caveats
The downside to decl is that it means its variables will start with "garbage" contents. For example, if we were to use:
new c = 5; new d; decl String:blah[512]; PrintToServer("%s", blah); Format(blah, sizeof(blah), "%d %d", c, d);
This code may crash the server, because blah may be completely corrupt (strings require a terminator, and that may not be present). Similarly, if we did:
new c = 5; decl d; decl String:blah[512]; Format(blah, sizeof(blah), "%d %d", c, d);
The value of d is now undefined. It could be any value, negative or positive.
Note that it is easy to efficiently make strings safe. The example below shows how to terminate a garbage string:
decl String:blah[512]; blah[0] = '\0';
Golden Rules
- Only use decl if in between declaring and loading/reading the value, you are absolutely sure there is at least one store/set operation that gives the variable valid data.
- Do not prematurely optimize. Likewise, there is no need to use decl on non-arrays, because there is no added expense for initializing a single cell value.
Notes
This example is NOT as efficient as a decl:
new String:blah[512] = "a";
Even though the string is only one character, the new operator guarantees the rest of the array will be zeroed as well.
Also note, it is invalid to explicitly initialize a decl:
decl String:blah[512] = "a";
The above code will not compile, because the purpose of decl is to avoid any initialization.
static
The static keyword is available at global and local scope. It has different meanings in each.
Global static
A global static variable can only be accessed from within the same file. For example:
//file1.inc static Float:g_value1 = 0.15f; //file2.inc static Float:g_value2 = 0.15f;
If a plugin includes both of these files, it will not be able to use either g_value1 or g_value2. This is a simple information hiding mechanism, and is similar to declaring member variables as private in languages like C++, Java, or C#.
Local static
A local static variable is a global variable that is only visible from its local lexical scope. For example:
MyFunction(inc) { static counter = -1; counter += inc; return counter; }
In this example, counter is technically a global variable -- it is initialized once to -1 and is never initialized again. It does not exist on the stack. That means each time MyFunction runs, the counter variable and its storage in memory is the same.
Take this example:
MyFunction(5); MyFunction(6); MyFunction(10);
In this example, counter will be -1 + 5 + 6 + 10, or 20, because it persists beyond the frame of the function. Note this may pose problems for recursive functions: if your function may be recursive, then static is usually not a good idea unless your code is re-entrant.
The benefit of a local static variable is that you don't have to clutter your script with global variables. As long as the variable doesn't need to be read by another function, you can squirrel it inside the function and its persistence will be guaranteed.
Note that statics can exist in any local scope:
MyFunction(inc) { if (inc > 0) { static counter; return (counter += inc); } return -1; }