Функции
Яблоки - это блоки, из которых состоит Я
Итак, функции. Они же процедуры. Признаюсь, я потратил некоторое время на размышления о выборе подходящего места для этой главы. По моей мысли, разобравшись с указателями, мы именно теперь подготовлены к наиболее полному освещению этой темы. Так, камешек к камешку, выводится кладка здания нашей книги.
Дефиниция
Что в программировании называют функцией? Вовсе не пустой вопрос. В математике, к примеру, функцией называют в широком смысле зависимость некоторых значений от некоторых аргументов. В программировании же функции - это логически завершённые блоки кода, выполняющие определённую работу. Они используются в первую очередь с целью избежания повторения однотипного кода в теле программы. Перечень переменных и их типы, которые принимает функция называются параметрами функции. Значения, присваиваемые параметрам, называются аргументами. Имена параметров и аргументов могут не совпадать, главное - это соблюдение типа. При этом функция может вовсе не использовать аргументов и не возвращать никаких значений. Сумма таких понятий, как имя функции, перечень её параметров и возвращаемых значений называется сигнатурой функции. В связи с тем, что в некоторых языках программирования существует так называемый полиморфизм функций, определение сигнатуры может в определённых случаях отличаться от приведенного выше. Таким образом функции - это в первую очередь элементы хорошо структурированного кода. Необходимо понимать, что большинство задач в вашей программе может быть выполнено и без применения хотя бы единственной функции. Однако, непременным свойством хорошего кода является выделение логически завершённых блоков и оформление их в виде функций. Это сродни хорошей литературе - когда мы сперва ясно осознаём и формулируем свои мысли, а затем складываем их в абзацы и предложения, придавая тексту структуру.
Между прочим, мы уже имели возможность косвенно познакомиться по крайней мере с одной функцией! Эта функция - main - точка входа в каждую нашу программу.
Особенности
Какие особенности присущи функциям в языке Go? Таких особенностей две. Первая из них - это возможность возвращения не одного, а нескольких значений, не прибегая к дополнительным инструментам, наподобие модификаторов параметров out в C#. Вторая особенность - это существование так называемых вариативных функций, то есть таких, которые могут принимать неопределённое число параметров. Но давайте обо всём по порядку. От простого - к сложному.
Простейшая функция - без параметров и возвращаемого значения.
Первое, что стоит рассмотреть, это простейшая функция, которая не имеет ни параметров, ни возвращаемых значений. Синтаксис такой функции будет состоять из ключевого слова func, имени функции, круглых скобок () и тела функции в фигурных скобках {}.
Конечно, для такой функции непросто придумать полезную работу, но, тем не менее мы постараемся. Как правило, это вывод на экран некоей информации. Пускай это будет таблица Пифагора:
Нередко это бывает циклически выводимое меню:
Функция, принимающая параметры и возвращающая значения
Синтаксически, в круглых скобках здесь добавится один или несколько параметров. После круглых скобок последует возвращаемое значение. Наличие возвращаемого значения подразумевает введение оператора return, которым должны завершиться все ветви кода в теле функции.
Если параметры однотипны, их можно перечислить через запятую, вот так:
Если возвращаемых значений несколько, они, как и параметры, берутся в круглые скобки. На практике, чаще всего, вторым параметром функция возвращает ошибку:
Приведём пример. Пускай наша функция производит деление с защитой от деления на 0:
Зачастую для обработки ошибок пишут отдельную функцию, чтобы избежать характерных для Go повторений блоков кода if err != nil { //..... Вот пример такой функции-обёртки:
Возвращаемые значения могут быть именованными. Синтаксически это выглядит так:
Изменим соответствующим образом функцию деления:
Использование именованных возвращаемых значений имеет определённый конкретный смысл и эффект. Будучи именованными, эти переменные изначально инициализируются значениями типа по умолчанию и сразу доступны в теле функции. Таким образом, нет необходимости объявлять дополнительные переменные. Но самый главный эффект проявляется в том случае, если нам нужно восстановиться после ошибки и всё же вернуть из функции некоторые значения. Об этом сейчас и поговорим.
Defer
В переводе с английского defer означает откладывать, отсрочивать. Ожидаемо, функции, объявленные с этим ключевым словом становятся отложенными. такая функция выполняется после выполнения всего следующего за ней блока кода. Например:
Таких функций может быть несколько, тогда они выполняются по принципу "всплывания" снизу вверх (LIFO - last in, first out):
Основных предназначений у этого приёма программирования - два. Первое, это "брошенное" закрытие открытых соединений:
Второе же применение - восстановление (recover) после ошибки (exception). Приведём пример восстановления после ошибки, с возвращением параметров:
Обратите внимание: без использования именованного возвращаемого значения (c commonResponse) было бы невозможно возвращение значений из defer функции при восстановлении после ошибки. Запомните этот пример. Это - классический способ обработки исключений в языке Go. Если вы хотите, чтобы ваша программа после критической ошибки продолжала работать - это как раз то, что вам нужно.
Анонимные функции
В примере с восстановлением после критической ошибки внимательный читатель, безусловно, обнаружил новую для себя конструкцию:
Что же, поздравляю, вы были весьма проницательны! У нас новая "нозологическая единица". Это - анонимная функция. Круглые скобки в конце означают выполнение кода "на месте". В простейшем виде выглядеть это будет так:
Чтобы сделать функцию более функциональной (масло масляное), в скобки можно поместить параметры:
Можно создать переменную - функцию:
Нередко анонимные функции используются как go-подпрограммы (goroutines, горутины). Но об этом следует поговорить несколько позже, в разделе о многопоточности.
Значения-функции
В последнем примере переменная f - представляет собой так называемое значение-функцию. Таким образом, функции (анонимные или нет), могут быть присвоены переменным. А ещё они могут быть, к примеру, переданы в другую функцию и возвращены из неё. Если функции возвращают функции, то они выстраиваются в так называемый «стек вызовов» по типу FIFO. Посмотрим, что из себя представляет переменная f:
Как видно, значение-функция, как и всякая переменная - это всегда определённый тип. Поэтому необходимо помнить о совместимости во время присвоения:
Нулевым значением типа функции является nil. Вызов нулевой функции приводит к аварийной ситуации. Значения-функции не являются сравниваемыми.
Рекурсия
Вероятно, это одна из самых нелюбимых и запутанных тем, относительно разбора предмета "функции". Чаще всего, от использования рекурсии можно безболезненно воздержаться, что не может не радовать. Однако, существуют классические алгоритмы, которые решаются именно применением рекурсии, и поэтому я не вижу причин не применять её, если контекст программы соответствует этому. Что есть рекурсивная функция? Это функция, которая в процессе работы вызывает сама себя. Не будем углубляться здесь в академические дебри обработки рекурсивных структур данных. Ограничимся классическим примером рекурсии - вычислением факториала:
Замыкания
Функцию, использующую переменные, определенные вне этой функции, называют замыканием. Замыкания и рекурсия считаются основными приёмами так называемого функционального программирования. Приведём простой пример:
Замыканием также является функция, возвращающая другую функцию:
Вариативные функции
В числе особенностей функций в Go мы упоминали так называемую вариативность. Рассмотрим это явление поближе. Вариативность функции - это возможность принимать переменное количество однотипных параметров. Синтаксически это выражается многоточием (...) перед указанием типа:
Количество передаваемых параметров может быть от нуля (ничего не передаём) и до некоего множества. Важно помнить, что вариативным может быть только единственный параметр, либо же последний в перечне, если параметров несколько:
На деле тип вариативного параметра - это срез! Убедимся в этом:
Возможность не передавать функции аргумент - бесценна. Она позволяет реализовать такую вещь, как параметры по умолчанию. Создадим, к примеру, функцию, которая будет производить паузу:
Иногда случается так, что мы ожидаем чтения данных, а данных не было. В таком случае нам также очень пригодится возможность опционально пропустить передачу аргумента соответствующему параметру.
Конструкция вариативной функции наводит на такую мысль: если истинным типом вариативного параметра является срез, то что, если передать такой функции срез в качестве аргумента. Что же, это вызовет ошибку, поскольку проектировщиками языка задумывалась передача именно перечня параметров, которая не тождественна срезу. Однако, есть способ это обойти. Синтаксис будет таков:
На этой ноте мы в общих чертах завершим тему о функциях. В добрый путь!
Раунд!
Last updated
Was this helpful?