Пятница, 29 Марта 2024, 03:51

Приветствую Вас Гость

Меню сайта
Категории каталога
Создание игр [354]
Статьи об общих понятиях связанных с созданием игр.
Программирование [82]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [144]
Статьи о программах для создания игр, уроки и описания.
Софт [39]
Различные программы, в том числе в помощь игроделам.
2D-графика [14]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [16]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [5]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [160]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [128]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Разработка игр для вас:
Всего ответов: 11092
Главная » Статьи » Программирование

Lua для всей семьи. Урок 4: пользовательские функции
Содержание:
1. Урок 1: установка и первые программы
2. Урок 2: условные ветвления и логика
3. Урок 3: циклы и математические функции

Тема "Функции" обычно очень тяжело даётся начинающим, поэтому я прошу задавать в комментариях вопросы, если вам что-то непонятно. Дело в том, что это очень важная тема, и чем лучше вы разберётесь с ней сейчас, тем меньше сложных проблем возникнет при написании программ.

UPD: заголовок изменён на более логичный, исправлены некоторые ошибки, из-за которых статья может быть понята неверно.

=== Урок №4 ===


В прошлый раз мы, помимо всего прочего, узнали о довольно большом количестве функций, упрощающих многие расчётные задачи. Функция - действительно мощный инструмент программирования, простой и удобный в использовании: вы просто передаёте ей параметр и получаете результат. В случае с математическими функциями (такими, как math.cos, math.sqrt), желаемым результатом является число, которое можно использовать в вычислениях; в случае с функцией print, давно и прочно засевшей в наших головах, желаемый результат - текст, появляющийся в окне консоли. Так как print не возвращает никакого значения (или, что то же самое, любой вызов print возвращает nil), но что-то полезное тем не менее делает, то говорят, что print вызывают ради побочного действия.

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

Типичный синтаксис определения функции:

Код

function <название> (<список_параметров>)
  <инструкции>
end


Чтобы стало ясно, о чём речь, вот пример программы с определённой в ней функцией:

Код

-- func.lua --

function hello()
  print "hello"
  print "hi"
end

-- Теперь вызывайте функцию hello хоть 50 раз подряд
hello()
hello()
hello()


Думаю, вопроса, что делает эта программа, возникнуть не должно. Заметьте: в заголовке функции в скобках отсутствуют какие-либо параметры, поэтому и вызывать её мы должны без параметров.

А таким образом определяется функция с параметрами:

Код

-- func2.lua --

function printmany(str, times)
  for i = 1, times do
   print(str)
  end
end

print("bug", 6)
print("goat", 2)


Функция printmany принимает строку, заданную первым параметром, и выводит её столько раз, сколько указано во втором параметре.
Таким образом, программа выведет 6 раз строку "bug" и 2 раза строку "goat", и если вы внимательно читали предыдущие уроки, вам должно быть понятно, почему. Понимать работу printmany следует так: при вызове print("bug", 6) внутри функции автоматически создаются переменные string = "bug" и times = 6, аналогичное происходит и при вызове print("goat", 2).

Заметьте, что в Lua нет никакой проверки типов: то есть, вы, в принципе, можете передать функции в качестве параметра любое значение, а не только то, которое она от вас вроде как ждёт. Это делает допустимыми следующие вызовы в предыдущей программе:

Код

printmany(78, 1) -- 1 раз выводит число 78
printmany(nil, 3) -- 3 раза выводит nil
printmany(false, 8) -- 8 раз выводит false


Однако такой номер не пройдёт:

Код

printmany("error", "seven")


В таком случае интерпретатор выдаст ошибку: он не сможет в цикле посчитать от одного до строки "seven". Увы, компьютеры порой так глупы! Что я хочу сказать: будьте внимательны, когда передаёте функции параметры, вместо вас за ними следить некому.

Число параметров в создаваемой функции может быть любым: хоть ноль, хоть два, хоть три, хоть три тысячи - впрочем, надеюсь, вам не захочется создавать функцию с тремя тысячами параметрами. Возможно создать функцию, которая принимала бы произвольное число параметров (наподобие print: вы можете передать ей столько значений, сколько вам вздумается), но об этом будет рассказано в одном из более поздних уроков: пока обойдёмся фиксированным.

Функции, описанные нами до этого, не возвращали никаких значений. Убедитесь сами:

Код

value1 = printmany("cat", 1)
value2 = math.cos(math.pi)
print(value1, value2)
-- Выведет: nil -1


Вернуть из функции значение можно с помощью инструкции return. Делается примерно так:

Код

-- vecfun.lua--

-- Функция возводит заданное число в квадрат
function square(x)
  return x^2
end

print(square(11))
-- Выведет 121


После return может стоять любое выражение, значение которого и будет возвращено функцией, а может не стоять вообще ничего, тогда функция просто завершится и возвратит nil.

Код

-- nothing.lua --

-- Этот код не делает вообще ничего!
-- return в данном случае, конечно,
-- можно просто убрать.

function nothing()
  return
end

nothing()


"Голый" return может использоваться, например, если вы хотите раньше времени завершить исполнение функции, подобно тому, как break используется для прерывания цикла. Можно также написать return nil, и смысл у этой инструкции будет такой же, как у простого return.

Функция не обязательно должна иметь один-единтсвенный return - вполне возможно создать несколько ветвей исполнения, каждая из которых возвращает своё значение. Например:

Код

-- sign.lua --

function sign(x)
  if x > 0 then
   return 1
  elseif x < 0 then
   return -1
  else
   return 0
  end
end

print(sign(9), sign(0), sign(-144))
-- Выведет: 1 0 -1


Вы наверняка уже догадались, что функция может возвращать не только числа, но и любые другие значения, которые поддерживаются языком Lua: например, логические значения (true и false), строки.

Код

-- logicfun.lua --

-- Та самая функция xor из одного из упражнений предыдущих уроков

fucntion xor(a, b)
 return (a or b) and not (a and b)
end


Не стесняйтесь использовать одни функции внутри других. Ведь в этом состоит один из секретов хорошего программирования: вы создаёте простые функции, из которых составляете более сложные, и в конце концов приходите к программе, эффективно решающей вашу задачу, к тому же легко читемую и изменяемую. Давайте с помощью составленной нами только что функции sign сделаем свой аналог функции math.abs (которую назовём просто abs):

Код

-- myabs.lua

function sign(x)
  if x > 0 then
   return 1
  elseif x < 0 then
   return -1
  else
   return 0
  end
end

function abs(x)
  return x*sign(x)
end

print(abs(8.88), abs(-8.88), abs(0))
-- Выведет 8.88 8.88 0


Не знаю, как вы, а моё сердце наполняется радостью при виде таких изящных решений.

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

Конечно, если бездумно использовать рекурсию, то функция так и будет без конца вызывать саму себя до тех пор, пока на компьютере не закончится оперативная память. Зрелище, не спорю, забавное, но такое поведение едва ли можно назвать желаемым. Если какую-то задачу планируют решать с помощью рекурсии, то в первую очередь обычно определяются граничные условия, а затем тело рекурсивного вычисления, которое с каждым новым вызовом приближается к граничным условиям. Классическое приложение рекурсивного вызова - функция, вычисляющая факториал. Как известно, факториал числа n (где n - натуральное число) определяется как:

n! = 1 * 2 * ... * n

Код

-- factorial.lua --

-- Рекурсивное вычисление
function fac1(n)
  if n <= 1 then
   return 1
  else
   return n*fac1(n-1)
  end
end

-- Итеративное вычисление
-- (с помощью цикла)
function fac2(n)
  fac = 1
  for i = 2, n do
   fac = fac * i
  end
  return fac
end


Хочу обратить ваше внимание: в итеративном варианте вычисления факториала мы создали переменную с названием fac, чтобы накапливать произведение чисел от одного до n. Всё бы хорошо, но эта переменная создаётся в глобальном окружении - для нас это означает, что любая определённая вне функции fac2 переменная с названием fac будет обречена на гибель после вызова функции fac2. Таким образом, если мы допишем:

Код

fac = 1961 -- год рождения вашей любимой тётушки Фак
print(fac2(6)) -- хотим увидеть 720 (6!)
print(fac) -- хотим увидеть 1961, но видим 720


то результат будет удручающим. С другой стороны, можно возразить: пример уж очень натянутый (ну у кого в мире может оказаться тётушка по имени fac!), и если что можно просто избежать создания лишней переменной с именем fac, и проблема не возникнет. Так-то оно так, но если вы пишете достаточно большую программу, "запрещённых" имён переменных со временем будет становиться всё больше и больше, и в конце концов нормальных имён попросту не останется.

Во избежание подобных окказий была разработана концепция видимости, которая в языке Lua поддерживается с помощью ключевого слова local: оно объявляет имя переменной, стоящее справа от него, локальным. Создавая переменную с ключевым словом local, вы указываете интерпретатору, что даже если в глобальном окружении и найдётся переменная с точно таким же именем, то мы не будем перезаписывать её, а на время сделаем вид, как будто её не существует. Использовать local крайне просто:

Код

-- factorial2.lua --

function fac2(n)
  local fac = 1 -- приписали local
  for i = 2, n do
   fac = fac * i
  end
  return fac
end

fac = 1961 -- не имеет ничего общего с
  -- локальной переменной fac в функции fac2
print(fac2(6)) -- выведет 720
print(fac) -- выведет 1961


Заметьте: локальная переменная fac существует до окончания функции fac2, а затем уничтожается. Переменная i в цикле for также является локальной, хотя вы и не указываете это явно, и сушествует до конца цикла. То есть, локальная переменная существует до конца блока, в котором она объявлена.

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

Напоследок хочется рассказать об одной интересной возможности Lua, которой порой не хватает во многих других языках программирования: функции Lua могут возвращать несколько значений. Блестящий пример применения этой возможности - стандартная функция math.modf, которая возвращает два числа: целую и дробную части переданного параметра.

Код

a, b = math.modf(1.2345)
-- Теперь a = 1, b = 0.2345


Чтобы ваша функция вернула несколько значений, достаточно указать их через запятую после инструкции return:

Код

-- severalreturnvalues.lua --

function several()
  return 1, 2, 3
end


Следующий фрагмент поможет вам понять специфику вызова функции several:

Код

a, b, c = several()
-- a = 1, b = 2, c = 3

d, e = several()
-- d = 1, e = 2, третье возвращаемое значение отброшено

f, g, h, i = several()
-- f = 1, g = 2, h = 3, i = nil: на i не хватило возвращаемых значений

_, _, j = several()
-- первые два значения отброшены (присвоены псевдопеременной _,
-- которая символизирует некую бездну для ненужных значений),
-- третье значение попало в переменную j: теперь j = 3


Лиц, знакомых с комплексным исчислением, заинтересует функция, представленная ниже: она принимает мнимую и реальную части комплексного числа и возвращает модуль и аргумент.

Код

-- complex.lua --

function modarg(re, im)
  return math.sqrt(re^2 + im^2), math.atan(im/re)
end

mod, arg = modarg(3, 4)
print(mod, math.deg(arg))
-- Вывод: 5 53.130102354156


Вернув аргументы в обратном порядке, можно обменять две переменные значениями:

Код

-- swap.lua --

function swap(a, b)
  return b, a
end

pip = 18
boy = 14
pip, boy = swap(pip, boy)
-- теперь: pip = 14, boy = 18


В принципе, для такого приёма создавать отдельную функцию swap не обязательно, сгодится и такая запись:

Код

pip, boy = boy, pip


Вот так! И никаких тебе лишних функций и временных переменных обмена - просто и понятно.

Подобным образом возможно инициализировать в одной строке сразу несколько переменных, вроде:

Код

danger, horror, terror = "bush", false, 2^16


но лично я не советую так делать, так как в такой записи очень просто нагородить чепухи, из-за которой программа не будет работать как надо. Гораздо нагляднее создавать переменные отдельно друг от друга:

Код

danger = "bush"
horror = false
terror = 2^16


Резюме:
1. Мы научились создавать свои собственные функции.
2. Узнали о ключевом слове local, о разделении переменных на локальные и глобальные и о том, почему последние менее предпочтительны.
3. Выяснили, как вернуть из функции сразу несколько значений и как легко обменять несколько переменных значениями без привлечения каких-либо избыточных ресурсов.


Упражнения, которые вам, возможно, не захочется выполнять, но я бы посоветовал:
1. Попробуйте вывести функцию с помощью print, например (заметьте, что скобки в данном случае опускаются):
Код

print(math.sqrt)
print(print)

Посмотрите, какой текст появится на экране. Далее совершите следующие присваивания:
Код

a = math.sqrt
math.sqrt = print

Теперь попробуйте каким-нибудь образом вызвать math.sqrt и a как функции. Что происходит? Какой вывод можно сделать?
2. Раскладывая синус в ряд Тейлора, можно получить следующее равенство:
Код

sin(x) = x - (x^3)/(3!) + (x^5)/(5!) - (x^7)/(7!) + ...

Многоточие в конце означает, что дальнейшие члены суммы подчиняются такой же закономерности, и их бесконечное число. Предлагаю вам написать свою версию функции math.sin, которая бы высчитывала синус по данной формуле. Используйте для вычисления факториала функцию fac1 из приведённого выше factorial.lua.
Категория: Программирование | Добавил: -l33t-h4xx- (18 Января 2014) | Автор: André Lipovaleau
Просмотров: 19448 | Комментарии: 5 | Рейтинг: 5.0/10 |
Теги: код, функции, скрипт, Урок, Для всего человечества, Lua-скрипт, кодинг, для всей семьи, программирвоание, LUA
Дополнительные опции:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:

Игровые объявления и предложения:
Если вас заинтересовал материал «Lua для всей семьи. Урок 4: пользовательские функции», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела. Предлагаются такие схожие материалы: Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.

Всего комментариев: 5
+1-
3 evgeny32   (17 Января 2019 20:21) [Материал]
Здравствуйте!
В примере ошибочка
Код

-- func2.lua --  

function printmany(str, times)  
  for i = 1, times do  
  print(str)  
  end  
end  

print("bug", 6) -- Должно быть printmany("bug", 6)
print("goat", 2) -- и быть printmany("goat", 2)

+1-
4 pl-by   (16 Октября 2020 18:51) [Материал]
Ошибки нету, по крайней мере в актуальной для времени написания статьи версии

+1-
5 pl-by   (16 Октября 2020 18:53) [Материал]
Извиняюсь, мысленно вырвал место ошибки из контекста.

+2-
1 aalla   (19 Января 2014 00:41) [Материал]
aallaзачем вообще писать функцию swap?
если и так все работает
Код

local a,b = 5,10
print(a,b)
a,b = b,a
print (a,b)

+2-
2 -l33t-h4xx-   (19 Января 2014 11:25) [Материал]
-l33t-h4xx-Для наглядноси.
Ниже я упомянул, что так делать не обязательно.

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • RPG Architect
  • ORTS
  • UkiRAD
  • Bladecoder Adventure Engine
  • C4 Engine
  • Point&Click Dev Kit
  • Pixel Vision 8
  • Phaser
  • J2DS
  • Genesis-3D
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2024 Рейтинг