Тема "Функции" обычно очень тяжело даётся начинающим, поэтому я прошу задавать в комментариях вопросы, если вам что-то непонятно. Дело в том, что это очень важная тема, и чем лучше вы разберётесь с ней сейчас, тем меньше сложных проблем возникнет при написании программ.
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: вы можете передать ей столько значений, сколько вам вздумается), но об этом будет рассказано в одном из более поздних уроков: пока обойдёмся фиксированным.
Функции, описанные нами до этого, не возвращали никаких значений. Убедитесь сами:
Вернуть из функции значение можно с помощью инструкции 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
Вы наверняка уже догадались, что функция может возвращать не только числа, но и любые другие значения, которые поддерживаются языком 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
Не знаю, как вы, а моё сердце наполняется радостью при виде таких изящных решений.
Чтобы вы знали, функция может даже вызывать саму себя! Эта техника называется рекурсивным вызовом, и она нередко используется во многих алгоритмах. Как правило, используя рекурсию там, где это возможно, можно получить очень маленький и простой код, а простота и компактность есть сверхцель любого программиста (если условия работы позволяют ему задумываться о таких вещах время от времени).
Конечно, если бездумно использовать рекурсию, то функция так и будет без конца вызывать саму себя до тех пор, пока на компьютере не закончится оперативная память. Зрелище, не спорю, забавное, но такое поведение едва ли можно назвать желаемым. Если какую-то задачу планируют решать с помощью рекурсии, то в первую очередь обычно определяются граничные условия, а затем тело рекурсивного вычисления, которое с каждым новым вызовом приближается к граничным условиям. Классическое приложение рекурсивного вызова - функция, вычисляющая факториал. Как известно, факториал числа 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
Вернув аргументы в обратном порядке, можно обменять две переменные значениями:
Код
-- 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. Раскладывая синус в ряд Тейлора, можно получить следующее равенство:
Многоточие в конце означает, что дальнейшие члены суммы подчиняются такой же закономерности, и их бесконечное число. Предлагаю вам написать свою версию функции math.sin, которая бы высчитывала синус по данной формуле. Используйте для вычисления факториала функцию fac1 из приведённого выше factorial.lua.
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:
Игровые объявления и предложения:
Если вас заинтересовал материал «Lua для всей семьи. Урок 4: пользовательские функции», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела.
Предлагаются такие схожие материалы:
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.