Difference between revisions of "Ru:Introduction to SourcePawn"

From AlliedModders Wiki
Jump to: navigation, search
(Usage)
(Strings)
Line 168: Line 168:
 
Выражения будут обсуждаться подробнее в конце статьи.
 
Выражения будут обсуждаться подробнее в конце статьи.
  
=Strings=
+
=Строки=
Strings are a convenient method of storing text. The characters are stored in an array. The string is terminated by a '''null terminator''', or a 0. Without a null terminator, Pawn would not know where to stop reading the string. All strings are UTF-8 in SourcePawn.
+
Строки являются удобным способом хранения текста. Символы хранятся в массиве. Строка ограничивается '''нулевым символом''', или 0. Без нулевого символа, Pawn не знает, где остановить чтение строки. Все строки в SourcePawn используют кодировку UTF-8.
  
Notice that Strings are a combination of arrays and cells. Unlike other languages, this means you must know how much space a string will use in advance. That is, strings are not dynamic. They can only grow to the space you allocate for them.
+
Отметим, что строки имеют комбинацию из массивов и однострочных переменных. В отличие от других языков, это означает, что Вы должны знать заранее, как много места будут использовать строки. Это означает, что строки не являются динамичными. Они могут лишь вырасти до размера, которым Вы их ограничили.
  
''Note for experts: They're not actually cells. SourcePawn uses 8-bit storage for String arrays as an optimization. This is what makes String a type and not a tag.''
+
''Примечание для специалистов: они фактически не однострочные. SourcePawn использует 8-битный строки для хранения массивов в качестве оптимизации. Это и есть то, что делает строки типом, а не меткой.''
  
==Usage==
+
==Использование==
Strings are declared almost equivalently to arrays. For example:
+
Строки были созданы почти в равной степени и для массивов. Например:
 
<pawn>
 
<pawn>
 
new String:message[] = "Hello!";
 
new String:message[] = "Hello!";
Line 182: Line 182:
 
</pawn>
 
</pawn>
  
These are equivalent to doing:
+
Это равносильно следующему:
 
<pawn>
 
<pawn>
 
new String:message[7], String:clams[6];
 
new String:message[7], String:clams[6];
Line 201: Line 201:
 
</pawn>
 
</pawn>
  
Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.
+
Хотя строки редко инициализируют таким образом, очень важно помнить о концепции нулевого символа, который свидетельствует о конце строки. Компилятор и большинство SourceMod функций будут автоматически остановлены нулевым символом, поэтому он является очень важным, при манипулировании строками напрямую.
  
Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.
+
Заметим, что строка должна быть заключена в двойных кавычках, а символ в одиночных.
  
 
==Characters==
 
==Characters==
Line 221: Line 221:
 
new clams[] = {'C', 'l', 'a', 'm', 's', 0};  //Valid, but NOT A STRING.
 
new clams[] = {'C', 'l', 'a', 'm', 's', 0};  //Valid, but NOT A STRING.
 
</pawn>
 
</pawn>
 
  
 
=Functions=
 
=Functions=

Revision as of 08:58, 24 December 2008

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

Это руководство не расскажет Вам как писать SourceMod плагины; оно предназначено для получения общих представлений о синтаксисе и семантике этого языка. Читайте отдельную статью Ru:Introduction to SourceMod Plugins (Введение в SourceMod плагины), для введения в SourceMod API.

Введение для новичков

Этот раздел создан не для программистов. Если Вы по прежнему в замешательстве, Вы можете прочитать книги о других языках программирования, таких как 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 функций будут автоматически остановлены нулевым символом, поэтому он является очень важным, при манипулировании строками напрямую.

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

Characters

A character of text can be used in either a String or a cell. For example:

new String:text[] = "Crab";
new clam;
 
clam = 'D';         //Set clam to 'D'
text[0] = 'A';      //Change the 'C' to 'A', it is now 'Arab'
clam = text[0];     //Set clam to 'A'
text[1] = clam;     //Change the 'r' to 'A', is is now 'AAab'

What you can't do is mix character arrays with strings. The internal storage is different. For example:

new clams[] = "Clams";                       //Invalid, needs String: type
new clams[] = {'C', 'l', 'a', 'm', 's', 0};  //Valid, but NOT A STRING.

Functions

Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or called, with parameters that give specific options.

There are two types of ways functions are called:

  • direct call - You specifically call a function in your code.
  • callback - The application calls a function in your code, as if it were an event trigger.

There are five types of functions:

  • native: A direct, internal function provided by the application.
  • public: A callback function that is visible to the application and other scripts.
  • normal: A normal function that only you can call.
  • stock: A normal function provided by an include file. If unused, it won't be compiled.
  • forward: This function is a global event provided by the application. If you implement it, it will be a callback.

All code in Pawn must exist in functions. This is in contrast to languages like PHP, Perl, and Python which let you write global code. That is because Pawn is a callback-based language: it responds to actions from a parent application, and functions must be written to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.

Declaration

Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the prototype and the body. The prototype contains the name of your function and the parameters it will accept. The body is the contents of its code.

Example of a function:

AddTwoNumbers(first, second)
{
  new sum = first + second;
 
  return sum;
}

This is a simple function. The prototype is this line:

AddTwoNumbers(first, second)

Broken down, it means:

  • AddTwoNumbers - Name of the function.
  • first - Name of the first parameter, which is a simple cell.
  • second - Name of the second parameter, which is a simple cell.

The body is a simple block of code. It creates a new variable, called sum, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the return statement, which tells the function to end and return a value to the caller of the function. All functions return a cell upon completion. That means, for example:

new sum = AddTwoNumbers(4, 5);

The above code will assign the number 9 to sum. The function adds the two inputs, and the sum is given as the return value. If a function has no return statement or does not place a value in the return statement, it returns 0 by default.

A function can accept any type of input. It can return any cell, but not arrays or strings. Example:

Float:AddTwoFloats(Float:a, Float:b)
{
   new Float:sum = a + b;
 
   return sum;
}

Note that if in the above function, you returned a non-Float, you would get a tag mismatch.

You can, of course, pass variables to functions:

new numbers[3] = {1, 2, 0};
 
numbers[2] = AddTwoNumbers(numbers[0], numbers[1]);

Note that cells are passed by value. That is, their value cannot be changed by the function. For example:

new a = 5;
 
ChangeValue(a);
 
ChangeValue(b)
{
   b = 5;
}

This code would not change the value of a. That is because a copy of the value in a is passed instead of a itself.

More examples of functions will be provided throughout the article.

Publics

Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from sourcemod.inc:

forward OnPluginStart();
forward OnClientDisconnected(client);

To implement and receive these two events, you would write functions as such:

public OnPluginStart()
{
   /* Code here */
}
 
public OnClientDisconnected(client)
{
   /* Code here */
}

The public keyword exposes the function publicly, and allows the parent application to directly call the function.

Natives

Natives are builtin functions provided by the application. You can call them as if they were a normal function. For example, SourceMod has the following function:

native FloatRound(Float:num);

It can be called like so:

new num = FloatRound(5.2);     //Results in num = 5

Array Parameters

You can pass arrays or Strings as parameters. It is important to note that these are passed by reference. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.

new example[] = {1, 2, 3, 4, 5};
 
ChangeArray(example, 2, 29);
 
ChangeArray(array[], index, value)
{
   array[index] = value;
}

The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:

example[2] = 29;

This is only possible because the array can be directly modified. To prevent an array from being modified, you can mark it as const. This will raise an error on code that attempts to modify it. For example:

CantChangeArray(const array[], index, value)
{
   array[index] = value;    //Won't compile
}

It is a good idea to use const in array parameters if you know the array won't be modified; this can prevent coding mistakes.

Expressions

Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.

The simplest expression is a single number. For example:

0;   //Returns the number 0
(0); //Returns the number 0 as well

Although expressions can return any value, they are also said to either return zero or non-zero. In that sense, zero is false, and non-zero is true. For example, -1 is true in Pawn, since it is non-zero. Do not assume negative numbers are false.

The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:

5 + 6;                   //Evaluates to 11
5 * 6 + 3;               //Evaluates to 33
5 * (6 + 3);             //Evaluates to 45
5.0 + 2.3;               //Evaluates to 7.3
(5 * 6) % 7;             //Modulo operator, evaluates to 2
(5 + 3) / 2 * 4 - 9;     //Evaluates to 7

As noted, expressions can contain variables, or even functions:

new a = 5 * 6;
new b = a * 3;      //Evaluates to 90
new c = AddTwoNumbers(a, b) + (a * b);

Operators

There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:

new a = 5;
 
a = a + 5;

Can be rewritten as:

new a = 5;
a += 5;

This is true of the following operators in Pawn:

  • Four-function: *, /, -, +
  • Bit-wise: |, &, ^, ~, <<, >>

Additionally, there are increment/decrement operators:

a = a + 1;
a = a - 1;

Can be simplified as:

a++;
a--;

As an advanced note, the ++ or -- can come before the variable (pre-increment, pre-decrement) or after the variable (post-increment, post-decrement). The difference is in how the rest of the expression containing them sees their result.

  • Pre: The variable is incremented before evaluation, and the rest of the expression sees the new value.
  • Post: The variable is incremented after evaluation, and the rest of the expression sees the old value.

In other words, a++ evaluates to the value of a while ++a evaluates to the value of a + 1. In both cases a is incremented by 1.

For example:

new a = 5;
new b = a++;   // b = 5, a = 6  (1)
new c = ++a;   // a = 7, c = 7  (2)

In (1) b is assigned a's old value before it is incremented to 6, but in (2) c is assigned a's new value after it is incremented to 7.

Comparison Operators

There are six operators for comparing two values numerically, and the result is either true (non-zero) or false (zero):

  • a == b - True if a and b have the same value.
  • a != b - True if a and b have different values.
  • a > b - True if a is greater than b
  • a >= b - True if a is greater than or equal to b
  • a < b - True if a is less than b
  • a <= b - True if a is less than or equal to b

For example:

(1 != 3);         //Evaluates to true because 1 is not equal to 3.
(3 + 3 == 6);     //Evaluates to true because 3+3 is 6.
(5 - 2 >= 4);     //Evaluates to false because 3 is less than 4.

Note that these operators do not work on arrays or strings. That is, you cannot compare either using ==.

Truth Operators

These truth values can be combined using three boolean operators:

  • a && b - True if both a and b are true. False if a or b (or both) is false.
&& 0 1
0 0 0
1 0 1
  • a || b - True if a or b (or both) is true. False if both a and b are false.
|| 0 1
0 0 1
1 1 1
  • !a - True if a is false. False if a is true.
! 0 1
1 0

For example:

(1 || 0);         //Evaluates to true because the expression 1 is true
(1 && 0);         //Evaluates to false because the expression 0 is false
(!1 || 0);        //Evaluates to false because !1 is false.

Left/Right Values

Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.

For example:

new a = 5;

In this example a is an l-value and 5 is an r-value.

The rules:

  • Expressions are never l-values.
  • Variables are both l-values and r-values.

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;
}