Функции — это блоки кода, выполняющие определенные операции.
main
Начальная точка исполнения, которая вызывается приблизительно на 70% загрузки карты и до её вызова запрещено создавать игровые объекты. В момент её вызова фреймы ещё не созданы, поэтому используйте таймер с нулевой задержкой.
void main(){
// Код начнёт исполняться здесь и можно создавать игровые объекты за исключением фреймов.
}
Вызов функции
Любую функцию можно вызвать из любой функции:
void main(){ // Эта функция будет вызвана интерпретатором
A(); // Вызываем объявленую ниже функцию A
}
void A(){
print("Эта функция выведет этот текст в консоль");
}
Рекурсия
Рекурсия это вызов функции из самой себя:
int A = 0;
void main(){
A++; // увеличиваем значение переменной A на единицу
print("Выводим этот текст в консоль");
if (A < 5) main(); // повторно вызываем функцию main если A меньше 5
}
Возвращаемое значение
Функции могут, но не обязаны возвращать значения. Для возврата значения используется ключевое слово return:
int A(){ // Объявляем функцию A которая возвращает значение типа int;
return 5; // Возвращаем значение типа int равное 5
}
int B = A(); // Присваиваем глобальной переменной B результат вызова функции A, тобишь 5
void
Чтобы обозначить, что функция ничего не возвращает используется ключевое слово void:
void main(){} // Эта функция никому ничего не возвращает
Возврат ссылки
Функции могут возвращаться ссылки используя оператор &:
int A; // Объявляем неинициализрованную переменную A типа int
int& B(){ // Объявляем функцию, которая вернёт ссылку на переменную типа int
return A;
}
void main(){
B() = 5; // Вызываем функцию B и устанавливаем возвращаемой переменной A значение 5
}
Для обеспечения гарантий, что ссылка не умрёт раньше времени установлены следующие правила для возвращаемых значений:
- ✅ Глобальные переменные разрешены потому что живут всё время исполнения кода.
- ✅ Члены класса разрешены потому что родительская функция будет хранить ссылку на экземпляр класса до своей смерти.
- ❌ Локальные переменные запрещены потому что будут очищены сразу после возврата.
Аргументы
Они же параметры - передаваемые в функцию значения для выполнения с ними операций.
void A(string B, string C) { // Объявление функции с именем A, которая принимает две строки B и C
print(B+" "+C); // Делаем конкатенацию строк и выводим результат в консоль
}
void main(){
A("Продам", "гараж"); // Вызываем функцию A и передаём ей две строки аргументами
}
Занимательная терминология
В древние времена, когда ещё мамонты людей ели, было принято соглашение что параметр это переменная объявленая в заголовке функции, а аргумент это собственно переданное значение.
void A (int B /* это параметр */ = 5 /* это аргумент */) {}
Но прогресс не стоит на месте и в один прекрасный момент прогрессивное осознало что такое именование избыточно и вносит путаницу. Поэтому было принято негласное соглашение, что аргумент и параметр это синонимы, а его значение называется просто - значение. Параметры, которые аргументы так же называют переменными функции ибо по сути они и являются переменными функции.
void A (int B /* это параметр или аргумент или переменная функции */ = 5 /* это значение */) {}
Чтоб не быть голословным, приведём несколько примеров
Математика
Без неё вообще бы не появилось этого вашего программирования, и функции там появились задолго до того, как электричество изобрели.
С++
Чтоб описать как этот язык повлиял на современность необходимо написать целую книгу. Так что обойдёмся одним словом - сильно.
На странице описания функций можно найти определение:
The function can be invoked, or called, from any number of places in the program. The values that are passed to the function are the arguments, whose types must be compatible with the parameter types in the function definition.
Но перейдя в описание перегрузок сразу видим таблицу.
Если мы явно различаем параметры и аргументы, то выходит что параметры вообще не учавствуют в перегрузке. Что явно не так.
Rust
Новый виток развития компилируемых языков, чья популярность тому доказательство. В описании функций они указали это различие явно:
We can define functions to have parameters, which are special variables that are part of a function’s signature. When a function has parameters, you can provide it with concrete values for those parameters. Technically, the concrete values are called arguments, but in casual conversation, people tend to use the words parameter and argument interchangeably for either the variables in a function’s definition or the concrete values passed in when you call a function.
Go
Непонятная поделка многомиллионной корпорации, которая популярна среди людей с особым складом ума. Яркий пример можно найти здесь:
Как так получается, что идентификатор id стал аргументом?
Python
Назван в честь Летающего цирка Монти Пайтона и пишут на нём такие же клоуны.
Хоть документации приведено различие между этими терминами, но это приводит к дублированию информации и плодит недопонимание:
PHP
Personal Home Page - язык для домашних страничек пользователей, который за неимением альтернатив используют большинство сайтов.
Читаем про допустимость запятой после параметров и вдруг видим аргументы.
JavaScript
Язык, которому место на свалке истории, но жадным капиталистам производителям браузеров наплевать на развитие.
Аргументы по умолчанию внезапно названы параметрами:
Dart
Разрабатывается как строго типизированная замена JavaScript, но не расчитывайте на то, что он появится в браузерах.
Документации мало, но я нашёл к чему докопаться.
Named parameters - все параметры именованые, если мы различаем термины, то нужно named argument.
Почему для named arguments используется paramName? Если мы различаем термины, то это ошибка.When calling a function, you can specify named arguments using paramName: value.
Здесь не использовали более точную формулировку default argument for a named parameter чтоб не получилось масло маслянное.To define a default value for a named parameter besides
const
Так же как и в переменных, ключевое слово const запрещает изменение значения аргумента в процессе исполнения функции.
void A(const int B){
B++; // Ошибка! Аргумент B объявлен как const
}
void main(){
A(5);
}
Начальные значения аргументов
Аргументам можно установить начальное значение которое будет подставлено компилятором если аргумент не будет передан. Если одному из аргументов указано начальное значение, то всем последующим аргументам оно так же должно быть установлено.
string A = "Москве";
void B(string C, string D="гараж", string E=A){
print(C+" "+D+" в "+E+"\n");
}
void main(){
B("Продам"); // Выведет: Продам гараж в Москве
B("Продам", "квартиру"); // Выведет: Продам квартиру в Москве
B("Пропью", "деньги", "кабаке"); // Выведет: Пропью деньги в кабаке
}
Начальное значение аргумента может включать переменные или вызов функций только если они доступны из глобальной области видимости.
Аргументы по ссылке
По умолчанию аргументы в функцию передаются по значению. Тобишь при входе в функцию значения всех переданных в функцию аргументов копируются.
Для расширения возможностей существует несколько типов передачи аргументов по ссылке:
Для расширения возможностей существует несколько типов передачи аргументов по ссылке:
in
Входящая ссылка только для чтения. При работе со строками может сэкономить вам памяти, так как не будет происходить копирование памяти. Использование с примитивами допустимо, но бесполезно.
void A(string& in B){
print(B);
}
string C = "Привет!";
void main {
A(C); // В функцию будет передана ссылка на переменную, копирование памяти не произойдёт.
A("Допустимо передавать значение напрямую без помещения в переменную");
}
const
При попытке присвоить значение входящей ссылке компилятор сгенерирует ошибку. Посему для пущей оптимизации и явного указания недопустимости записи рекомендуется явно указывать const:
void A(string& in B, const string& in C){
B = "Ошибка! Запрещено присваивать значение входящим ссылкам!";
C = "Ошибка! Аргумент C объявлен как const"
}
out
Ссылка для записи выходного значения. Обычно используется для возвращения дополнительных значений.
int divide(int a, int b, int& out remainder) {
remainder = a % b; // записываем остаток от деления в переменную для выхода
return a / b; // возвращаем результат деления
}
void main() {
int remainder;
int result = divide(10, 3, remainder);
}
С помощью ключевого слова void можно сделать выходной параметр необязательным:
int divide(int a, int b, int& out remainder = void) {
remainder = a % b; // записываем остаток от деления в переменную для выхода
return a / b; // возвращаем результат деления
}
void main() {
int result = divide(10, 3);
}
inout
Ссылка для входа-выхода. Просто указывает на фактическое значение. Для обеспечения гарантии того, что ссылка будет существовать всё время существования функции разрешено передавать только ссылочные типы. Помимо &inout существует короткая запись в виде &:
funcdef void A(); // Объявляем сигнатуру функции. Читайте ниже.
void B(){
print("Функция вызвана по ссылке");
}
void C(A& D){
D(); // Вызываем функцию, переданную по ссылке
};
void main(){
C(@B); // Передаём функцию по ссылке
}
funcdef
Объявляет сигнатуру функции для хранения функции в переменной или передачи аргументом.
funcdef void A(string); // Объявляем сигнатуру функции
void B(string C) { // Объявляем функцию, подходящую под сигнатуру
print(C);
}
void D(A& E) { // Объявляем функцию, которая принимает функцию, подходящую под сигнатуру
E("Вызываем функцию, переданную по ссылке");
}
void main(){
A@ F = @B; // Объявляем переменную где тип является сигнатурой функции
D(@F); // Передаём переменную в функцию
}
Перегрузка
Объявление нескольких функций в одном пространстве имён с одинаковым именем и различным набором аргументов называется перегрузкой функции.
void Log(string Msg){
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, Msg);
}
void Log(int Msg){
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, formatInt(Msg));
}
void main(){
Log("1"); // Будет вызвана: void Log(string Msg)
Log(1); // Будет вызвана: void Log(int Msg)
}
Перегрузки размечаются на этапе компиляции и компилятор пытается найти самую подходящую функцию для вызова основываясь на типах параметров. Тип возвращаемого значения не влияет на выбор функции.
Приведение типов
При передаче аргумента в функцию происходит неявное приведение типов:
void A(int64 B){
print(B);
}
void main(){
A(1); // Передаём числовой литерал, который расценивается как int32
}
При выборе подходящей функции происходит подсчёт стоимости преобразования каждого аргумента. Если по итогу окажется две функции с одинаковой стоимостью, то будет сгенерирована ошибка.
const
Стоимость const учитывается при расчёте, но при идентичных типах приводит к неоднозначности:
void A(int B){}
void A(const int B){}
void main(){
A(1); //Ошибка! Две функции подходят под условие перегрузки
}
Ссылки
Ссылки учитываются при расчёте, но при идентичных типах приводят к неоднозначности:
void A(int B){}
void A(int& B){}
void main(){
A(1); //Ошибка! Две функции подходят под условие перегрузки
}
Числа
Все числа совместимы. В некоторых случаях с потерей байт или изменением значения.
Преобразования происходят в следующем порядке:
Порядок | Преобразование | Пример |
---|---|---|
1 | enum в целочисленное того же размера | enum int32 |
2 | enum в целочисленное другого размера | enum int64 |
3 | Увеличение размерности числа | float double |
4 | Уменьшение размерности числа | int64 int32 |
5 | Знаковое целочисленное в беззнаковое | int32 uint32 |
6 | Беззнаковое целочисленное в знаковое | uint32 int32 |
7 | Целочисленное в вещественное | int32 float |
7 | Вещественное в целочисленное | double int64 |
Преобразование происходят последовательно, ниже приведено несколько примеров:
Цель преобразования | Цепочка преобразования |
---|---|
enum int64 | enum int64 |
int8 uint64 | int8 int16 int32 int64 uint64 |
double uint16 | double int64 int32 int16 uint16 |
int32 uint64 | int32 int64 uint64 |
Поиск функции
После нахождения всех подходящих функций для каждой позиции аргумента будет построен список подходящих функций. Возьмём для примера несколько функций:
void A(int8 B, int16 C, int32 D){}; // Кандидат 1
void A(int32 B, int16 C, int8 D){}; // Кандидат 2
void A(string B, int16 C, int8 D){}; // Кандидат 3
Построим таблицу для таких аргументов:
A(int16(1), int16(1), int32(1));
Первый аргумент: int16 | Второй аргумент: int16 | Третий аргумент: int32 | Количество преобразований |
---|---|---|---|
Кандидат 1: int16 int8 | Кандидат 1 | Кандидат 1 | 1 |
Кандидат 2: int16 int32 | Кандидат 2 | Кандидат 2: int32 int16 int8 | 3 |
❌ | Кандидат 3 | Кандидат 3: int32 int16 int8 | ❌ |
Наименьшее количество преобразований имеет Кандидат 1, он и будет вызван.
Теперь сменим типы параметров и добьёмся неоднозначности:
A(int16(1), uint16(1), int16(1));
Первый аргумент: int16 | Второй аргумент: uint16 | Третий аргумент: int16 | Количество преобразований |
---|---|---|---|
Кандидат 1: int16 int8 | Кандидат 1: int16 uint16 | Кандидат 1: int16 int32 | 3 |
Кандидат 2: int16 int32 | Кандидат 2: int16 uint16 | Кандидат 2: int16 int8 | 3 |
❌ | Кандидат 3: int16 uint16 | Кандидат 3: int16 int8 | ❌ |
Две функции с одинаковым количеством преобразований приведут к неоднозначности и будет сгенерирована ошибка.
Анонимные функции
Они же лямбды - функции объявляемые локально.
funcdef bool Comparator(int a, int b); // Объявляем сигнатуру функции сравнения
bool Compare(int a, int b, Comparator @comparator){ // Объявляем функцию которая сравнивает два числа используя функцию сравнения
return comparator(a,b);
}
void main() {
// Сравниваем числа и ложим результат в переменные
bool result1 = Compare(1, 2, function(a,b){ return a == b; });
bool result2 = Compare(3, 4, function(a,b){ return a != b; });
}
Анонимная функция будет использовать сигнатуру описанную в funcdef поэтому возвращаемый тип и типы аргументов можно опустить.
Неоднозначность
Если функция перегружена и существует несколько вариантов использования, то для избежания неоднозначности можно указать тип аргумента. Напоминаю, что возвращаемый тип не участвует в выборе подходящей функции при перегрузке.
funcdef void A(int);
funcdef void B(float);
void Overloaded(A@) {}
void Overloaded(B@) {}
void main() {
Overloaded(function(int a) {});
}
Ред. ScorpioT1000