Функция — это логически целостный участок кода с одним входом и одним выходом в потоке управления. Этот участок можно использовать многократно, обращаясь к нему по имени.
Смысловая нагрузка у функции в программировании примерно такая же, как у функции в математике. У функции есть название и определение. Ей можно передать значения переменных и получить результат.
Сначала нужно определить функцию:
И только затем её можно использовать:
Декларация функции в Go
Декларацию функции часто называют сигнатурой (signature).
Здесь:
MyFunction
— имя функции.arg1 arg1type
— параметр функции и его тип. Типы параметров должны быть заявлены при декларации, потому что Go — статически типизированный язык.resultType
— тип возвращаемого значения.
Результат функции тоже можно именовать:
Note
Обратите внимание, что нельзя декларировать функцию внутри другой функции. Это ограничение языка, связанное с особенностями компиляции.
Параметры
Аргументы передаются функции путём копирования значения, то есть функции не могут изменять переданные им переменные.
Для примера напишем такой код:
Получим:
5
Если параметры одного типа, можно сократить код:
Note
В Go есть специальный синтаксис для функций, которые можно вызывать с переменным количеством аргументов (variadic functions). Параметр, принимающий такие аргументы, нужно поставить последним в списке, а перед его типом — многоточие.
Внутри функции этот параметр рассматривается как нумерованная последовательность аргументов (slice).
Вызывают такую функцию обычным образом, со списком аргументов через запятую:
Если вызвать эту функцию без аргументов Sum()
, параметр x
примет значение nil
. Тогда цикл не пройдёт ни одной итерации, и функция вернёт 0
.
Возвращаемые значения
Функция необязательно возвращает значение. Она может использоваться исключительно ради побочных эффектов, производимых ею в среде исполнения. Например, fmt.Println()
.
Также функция может возвращать не одно, а несколько значений разных типов.
При вызове такой функции должны быть предоставлены переменные, которым надо все эти значения присвоить.
А если некоторые значения не нужны, можно воспользоваться переменной _
.
В функции, которая возвращает значение, обязательно должна быть инструкция return
.
Список возвращаемых значений имеет тот же синтаксис, что и список параметров. Например, можно написать коротко:
Обратите внимание, что здесь возвращаемые значения имеют имена. В теле функции они могут использоваться как обычные переменные. При входе в функцию они будут инициализированы значениями по умолчанию для данного типа.
Если функция содержит именованные возвращаемые значения, то необязательно указывать список возвращаемых значений оператором return
. В таком случае будут возвращены значения переменных, которые они имеют к этому моменту.
Вот функция, которая находит индекс буквы в строке и возвращает вторым аргументом false
, если буква не найдена:
Если количество и тип возвращаемых функцией значений
в точности соответствуют параметрам другой функции,
то допускается такой синтаксис вызова:
Рекурсивные функции
В#Go можно декларировать рекурсивную функцию — вызывающую саму себя.
Вот хрестоматийный пример рекурсивного вычисления n!
, факториала числа:
А вот числа Фибоначчи:
Следует помнить, что в Go вызов функции имеет определённую вычислительную стоимость, а также затраты по памяти, ведь как минимум нужно скопировать аргументы. Поэтому множество вложенных вызовов функции может привести к снижению производительности программы и переполнению памяти.
Итеративные алгоритмы будут работать быстрее. Для сравнения приведём итеративную реализацию (на основе циклов) вышеуказанных примеров:
Тем не менее это не означает, что рекурсивные алгоритмы неприменимы. В ряде случаев они могут быть полезнее, проще и делать код нагляднее.
Приведём пример работы с рекурсивным обходом всех файлов в данной директории, причём директория может содержать вложенные поддиректории:
Итеративная реализация данного алгоритма была бы куда сложнее.
Функция первого класса
Функции в Go ничем не уступают другим классам объектов. У функции есть тип и значение. Функцию можно присвоить переменной, можно передать аргументом другой функции. Функция может возвращать в качестве значения другую функцию.
Тип функции виден в её сигнатуре, то есть определяется как набор типов и количества аргументов, возвращаемых значений.
Например, эта функция
имеет тип:
Можно присвоить её переменной такого типа:
Можно написать функцию высшего порядка с параметром такого типа:
И передать ей функцию аргументом:
Для функции есть литеральная форма синтаксиса. Функцию можно создать по месту, не декларируя и не именуя в блоке деклараций.
Можно даже использовать литерал в качестве аргумента при вызове:
Это то, что ещё называют анонимной или лямбда-функцией.
Можно написать функцию, которая возвращает функции значениями:
И вызывать вот так:
Замыкания
Go — язык с лексической областью видимости (lexically scoped). Это значит, что переменные, определённые в окружающих блоках видимости (например, глобальные переменные), доступны функции всегда, а не только на время вызова. Можно считать, что функция их запоминает.
Лексическая область видимости и анонимные функции позволяют реализовать замыкания (closure).
Вот классический пример итератора чётных чисел, построенного на замыкании:
Замыкание привязывает к себе внешнюю переменную. После выхода из внешней функции Generate
она не уничтожается, а остаётся привязанной к функции замыкания, причём её значение сохраняется между вызовами функции.
Получаем:
0
2
4
6
8
А вот и упоминавшиеся числа Фибоначчи, но теперь написанные с применением замыкания:
Получаем:
1
1
2
3
5
8
13
Такие функции иногда называют генераторами. Они выдают новое значение какой-либо последовательности при каждом вызове.
Замыкания довольно полезные. Они позволяют просто и изящно реализовать определённые паттерны проектирования. Тем не менее, чтобы эффективно использовать замыкания, надо представлять, как они работают.
Приведём более практичный пример использования замыкания. Создадим две функции-обёртки, одна из которых будет подсчитывать количество вызовов, а вторая — время исполнения функции.
Note
Такой подход часто применяется в веб-разработке на Go, когда группа функций-обработчиков объединяется в цепочки, разделяя между собой ответственность за определённые действия.
Особенные функции
Точка входа в программу — функция main()
. Она обязательно должна существовать в единственном виде и в любой исполняемой программе на Go. main()
не принимает аргументов и не возвращает значений.
В Go есть встроенные функции, например: make()
, new()
, len()
, cap()
, delete()
, close()
, append()
, copy()
, panic()
, recover()
. Это не библиотечные функции. Они не вполне подчиняются правилам для функций пользователя. У них может не быть сигнатуры, а их использование документировано в спецификации языка — основополагающем для Go документе.
В базовом синтаксисе языка также описана вот эта функция:
В пакете и даже в одном файле можно декларировать несколько таких функций. Они будут вызваны один раз при инициализации пакета, после присвоения глобальных переменных, в том порядке, в котором они предоставлены компилятору (встречаются в исходном тексте). Прямой вызов функции init()
в коде программы не предусмотрен.
Служат эти функции для создания окружения, необходимого пакету для корректной работы.
Вот простой пример:
Конструктор с опциями (funcopts)
Напишем конструктор типа с начальными значениями и удобными опциями. Воспользуемся подходом, который предложил Роб Пайк в статье Self-referential functions and the design of options.
В Go нет конструкторов в классическом ООП-понимании. Есть встроенные аллокаторы make()
и new()
, которые инициализируют поля в их нулевые значения. Необходимые параметры устанавливаются литерально, присваиванием.
Это не очень технологично. Предположим, нам нужно инициализировать однотипные элементы значениями по умолчанию, но с возможностью задать некоторые параметры.
Сделаем свой конструктор с опциями.
Здесь опции — это функции, применяемые к объекту. За это подход получил название funcopts.
Чтобы устанавливать параметры, будем использовать функции высшего порядка, возвращающие значениями функции option
.
Тогда инициализация объекта конструктором будет выглядеть так:
📂 Go | Последнее изменение: 20.08.2024 15:05