golang функции с параметрами
Функции первого класса, замыкания и анонимные функции в Golang
После изучения данного урока вы сможете:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
В данном уроке раскрывается потенциал использования функций первого класса как элементов теоретической программы «Станции экологического мониторинга Ровер (REMS)», что считывает данные температурных сенсоров.
Для сравнения подумаем о примере из реального мира. Мясо будет вкуснее с кетчупом. После приготовления мясного блюда вы можете найти домашний рецепт кетчупа, но зачем все так усложнять — сходите за кетчупом в обычный магазин.
Помимо рецептов и сенсоров температуры, какие другие примеры изменения функции с помощью другой функции вы можете привести?
Присваивание функции переменной в Go
Сенсоры станции погоды предоставляют данные о температуре воздуха в диапазоне 150–300° K. У нас есть функции для конвертации градусов Кельвина в другие единицы измерения при наличии данных, однако при отсутствии специального сенсора, встроенного в компьютер (или Raspberry Pi), считывающего информацию, это может стать проблематично.
Пока мы можем использовать фальшивый сенсор, что будет возвращать псевдослучайные числа, однако в таком случае нужно будет найти способ использовать realSensor или fakeSensor взаимозаменяемо. В следующем примере именно это происходит. При создании такой программы различные реальные сенсоры также могут быть подключены, к примеру, для сбора данных как о температуре земли, так и для температуры воздуха.
Функции
Функция является независимой частью кода, связывающей один или несколько входных параметров с одним или несколькими выходными параметрами. Функции (также известные как процедуры и подпрограммы) можно представить как черный ящик:
До сих пор мы писали программы, используя лишь одну функцию:
Но сейчас мы начнем создавать код, содержащий более одной функции.
Ваша вторая функция
Вспомните эту программу из предыдущей главы:
Эта программа вычисляет среднее значение ряда чисел. Поиск среднего значения — основная задача и идеальный кандидат для вынесения в отдельную функцию.
Теперь давайте перенесём часть кода из функции main в функцию average :
Запуск этой программы должен дать точно такой же результат, что и раньше. Несколько моментов, которые нужно иметь ввиду:
имена аргументов не обязательно должны совпадать с именами переменных при вызове функции. Например, можно сделать так:
и программа продолжит работать;
функции не имеют доступа к области видимости родительской функции, то есть это не сработает:
Как минимум нужно сделать так:
функции выстраиваются в «стек вызовов». Предположим, у нас есть такая программа:
Её можно представить следующим образом:
Каждая вызываемая функция помещается в стек вызовов, каждый возврат из функции возвращает нас к предыдущей приостановленной подпрограмме;
можно также явно указать имя возвращаемого значения:
Возврат нескольких значений
Go способен возвращать несколько значений из функции:
Возврат нескольких значений часто используется для возврата ошибки вместе с результатом ( x, err := f() ) или логического значения, говорящего об успешном выполнении ( x, ok := f() ).
Переменное число аргументов функции
Существует особая форма записи последнего аргумента в функции Go:
Это похоже на реализацию функции Println :
Функция Println может принимать любое количество аргументов любого типа (тип interface мы рассмотрим в главе 9).
Замыкания
Возможно создавать функции внутри функций:
add является локальной переменной типа func(int, int) int (функция принимает два аргумента типа int и возвращает int ). При создании локальная функция также получает доступ к локальным переменным (вспомните области видимости из главы 4):
Функцию, использующую переменные, определенные вне этой функции, называют замыканием. В нашем случае функция increment и переменная x образуют замыкание.
Один из способов использования замыкания — функция, возвращающая другую функцию, которая при вызове генерирует некую последовательность чисел. Например, следующим образом мы могли бы сгенерировать все четные числа:
Рекурсия
Наконец, функция может вызывать саму себя. Вот один из способов вычисления факториала числа:
factorial вызывает саму себя, что делает эту функцию рекурсивной. Для того, чтобы лучше понять, как работает эта функция, давайте пройдемся по factorial(2) :
Замыкание и рекурсивный вызов — сильные техники программирования, формирующие основу парадигмы, известной как функциональное программирование. Большинство людей находят функциональное программирование более сложным для понимания, чем подход на основе циклов, логических операторов, переменных и простых функций.
Отложенный вызов, паника и восстановление
defer часто используется в случаях, когда нужно освободить ресурсы после завершения. Например, открывая файл необходимо убедиться, что позже он должен быть закрыт. C defer это выглядит так:
Паника и восстановление
Но в данном случае recover никогда не будет вызвана, поскольку вызов panic немедленно останавливает выполнение функции. Вместо этого мы должны использовать его вместе с defer :
Паника обычно указывает на ошибку программиста (например, попытку получить доступ к несуществующему индексу массива, забытая и непроинициализированная карта и т.д.) или неожиданное поведение (исключение), которое нельзя обработать (поэтому оно и называется «паника»).
Задачи
Функция sum принимает срез чисел и складывает их вместе. Как бы выглядела сигнатура этой функции?
Напишите функцию с переменным числом параметров, которая находит наибольшее число в списке.
Что такое отложенный вызов, паника и восстановление? Как восстановить функцию после паники?
Необязательные аргументы в функциях Go
В Go нет синтаксиса для определения необязательных аргументов в функциях, поэтому приходится использовать обходные пути. Я знаю 2:
Второй способ в принципе делает тоже самое, но с синтаксическим сахаром. Мне не давала покоя мысль, а сколько же стоит этот сахар, кому ещё интересно прошу под кат.
Для тестов я использовал структуру с 10 опциями:
и 2 пустые функции:
Для тех, кто не работал с функциональными аргументами немного расскажу как они работают. Каждая опция описывается в виде функции, которая возвращает функцию, которая изменяет структуру с параметрами, например:
где OptsFunc — это type OptsFunc func(*Opts)
При вызове функции их передают в качестве аргументов, а внутри функции в цикле заполняют структуру с аргументами:
Здесь магия и заканчивается, теперь у нас есть заполненная структура, осталось только выяснить, сколько стоит сахар. Для этого я написал простой benchmark:
Для тестирования я использовал Go 1.9 на Intel® Core(TM) i7-4700HQ CPU @ 2.40GHz.
BenchmarkStructOpts-8 100000000 10.7 ns/op 0 B/op 0 allocs/op
BenchmarkWithOpts-8 3000000 399 ns/op 240 B/op 11 allocs/op
Результаты противоречивые, с одной стороны разница почти в 40 раз, с другой — это сотни наносекунд.
Мне стало интересно, а на что же тратится время, ниже вывод pprof:
Всё логично, время тратится на выделение памяти под анонимные функции, а как известно malloc — это время, много времени…
Для чистоты эксперимента я проверил, что происходит при вызове без аргументов:
Здесь разница немного меньше, примерно в 20 раз:
BenchmarkEmptyStructOpts-8 1000000000 2.75 ns/op 0 B/op 0 allocs/op
BenchmarkEmptyWithOpts-8 30000000 57.0 ns/op 80 B/op 1 allocs/op
Функции в Golang на примерах
Под конец данного урока вы сможете:
Данный урок начинается с разбора документации стандартной библиотеки для функций, что были использованы в ранних уроках.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
Познакомившись с синтаксисом создания функций, мы напишем функции для программы анализа погоды. Станция экологического мониторинга Ровер (REMS) собирает данные о погоде на поверхности Марса. Мы создадим функции, что в теории могли бы стать частью программы REMS — к примеру, конвертер температуры.
Рассмотрим ситуацию, когда вам нужно сделать бутерброд. Кажется, будто это просто, но все-таки процесс готовки строится из нескольких этапов. Помыть овощи, нарезать все ингредиенты и так далее. Если вдаваться в более глубокие детали, можно упомянуть сбор пшеницы, превращение ее в муку и выпечку хлеба. Однако данные подготовительные функции вас не касаются, они являются частью работы фермера и пекаря.
Можно посмотреть на описанный процесс через призму функций для каждого шага. Таким образом, если в будущем вам понадобятся кусочки помидоров для пиццы, оставшиеся ингредиенты можно будет использовать для нового блюда.
Наверняка вы ежедневно сталкиваетесь с примерами вещей, которые можно использовать повторно. Подумайте, какие из данных задач можно разбить на функции?
Объявление функции в Golang
В документации Go значится список функций, что объявляются в каждом пакете стандартной библиотеки. Функций довольно много, и охватить их все в одном уроке просто невозможно.
Объявление Intn из пакета rand выглядит следующим образом:
Освежим в памяти способ использования функции Intn :
Объявление и вызов функции Intn
Функция Intn принимает один параметр, что заключен в скобки. Параметром является название переменной, затем указывается тип и объявляется переменная:
На заметку: Параметр и аргумент являются математическими терминами, но есть небольшое различие. Функция принимает параметры и вызывается с аргументами, хотя в некоторых случаях данные термины используются как взаимозаменяемые.
Далее дан пример вызова функции Unix с двумя аргументами, что соответствуют параметрам sec и nsec :
На заметку: В следующем уроке мы рассмотрим способы объявления новых типов вроде time.Time и big.Int.
Функция Unix принимает два параметра одинакового типа:
Когда параметры значатся в списке при объявлении функции, вам нужно уточнить тип, когда тот меняется. Это делается следующим образом:
На заметку: В документации Go иногда даются небольшие примеры, в которые можно добавить нужные вам детали. Дополнительные примеры также можно найти на gobyexample.com. Если вы уже работаете над собственными проектами, данные примеры могут прийтись кстати.
Во многих языках программирования есть функции, что принимают несколько параметров, однако Go также возвращает несколько результатов. Рассмотренная в одном из предыдущих уроков функция Atoi конвертирует строку в число и возвращает два результата, что в следующем примере присваиваются переменным countdown и err :
В документации для пакета strconv функция Atoi объявляется следующим образом:
Обратите внимание, что тип error является встроенным типом для указания ошибок.
С самого начала изучения Golang мы использовали функцию Println. Это универсальная функция в том плане, что она может принимать как один, так и несколько параметров. Она также принимает параметры разных типов, включая целые числа и строки:
Объявление функции из документации, может показаться несколько странным, так как здесь используются некоторые аспекты, которые мы еще не изучали:
Вопросы для проверки:
Создание функции в Golang
Данные сенсора температуры должны быть получены в единицах измерения, понятных для землян. Сенсоры считывают температуры по шкале Кельвина, где 0° K является абсолютным нулем, или минимальной возможной температурой. Функция в следующем листинге конвертирует температуру из градусы Кельвина в градусы Цельсия. После создания, функция может использоваться вновь, когда потребуется конвертация температуры.
Обратите внимание, что функции внутри одного и того же пакета вызываются без уточнения названия пакета.
Преимущества изоляции функции в Golang
Функция kelvinToCelsius из Листинга 1 изолирована от других функций. Ее единственной входной информацией является принимаемый параметр, а единственным выводом является возвращаемый результат. Он не вносит изменения во внешнее состояние программы. У таких функций нет побочных эффектов, они являются наиболее простыми для понимания, проверки и повторного использования.
Мы дали переменным разные названия, однако передача значений предполагается даже в том случае, когда у аргументов и значений одинаковые названия.
Плюс ко всему переменная под названием k в kelvinToCelsius является полностью независимой от других переменных под названием k в других функциях, что возможно благодаря области видимости переменной. У параметров при объявлении функции и переменных, объявленных внутри тела функции, есть область видимости функции. Переменные, объявленные в других функциях являются полностью независимыми, даже если у них одно и то же название.
Вопрос для проверки:
В чем преимущества разделения кода на функции?
Функции можно использовать повторно. они обеспечивают изоляцию переменных внутри области видимости функции, а также позволяют дать действию понятное название, что позволяет создать более легкий для чтения код.
Заключение
Итоговое задание для проверки:
Используйте Go Playground и модифицируйте Листинг 1 для добавления дополнительных функций конвертирования температуры:
Вы использовали kelvinToCelsius и celsiusToFahrenheit в своей новой функции или написали независимую функцию с новой формулой? Оба подхода подойдут.
Использование функций с переменным количеством аргументов в Go
Published on January 24, 2020
Введение
Функция с переменным количеством аргументов — это функция, которая принимает ноль, одно или больше значений в качестве одного аргумента. Хотя функции с переменным количеством аргументов встречаются редко, их можно использовать, чтобы сделать код более чистым и удобным для чтения.
Создадим программу, которая использует функцию fmt.Println и передает ноль, одно или несколько значений:
Запустим программу с помощью следующей команды:
Результат должен выглядеть так:
Мы показали, как вызывать функцию с переменным количеством аргументов, а теперь посмотрим, как можно определить собственную функцию с переменным количеством аргументов.
Определение функции с переменным количеством аргументов
Если мы запустим программу, результат будет выглядеть так:
Изменим программу так, чтобы она определяла, что никакие значения в нее не отправляются:
Использование параметра с переменным количеством аргументов делает код удобнее для чтения. Создадим функцию, объединяющую слова с заданным разделителем. Вначале мы создадим эту программу без функции с переменным количеством аргументов, чтобы показать, как будет проводиться чтение:
Теперь напишем ту же функцию, но как функцию с переменным количеством аргументов:
Если мы запустим эту программу, результат будет выглядеть как предыдущая программа:
Хотя обе версии функции join выполняют одно и то же с программной точки зрения, версия функции с переменным количеством аргументов намного проще читается при вызове.
Порядок при переменном количестве аргументов
В функции может быть только один параметр с переменным количеством аргументов, и это должен быть последний определяемый в функции параметр. Определение параметров в функции с переменным количеством аргументов в любом другом порядке вызовет ошибку при компиляции:
При определении любой функции с переменным количеством аргументов только последний параметр может иметь переменное количество аргументов.
Раскрывающиеся аргументы
Мы показали, что в функцию с переменным количеством аргументов можно передать ноль, одно или несколько значений. Однако бывает и так, что нам нужно отправить в функцию с переменным количеством аргументов целый срез значений.
Возьмем функцию join из последнего раздела и посмотрим, что получится:
Если мы запустим эту программу, то получим ошибку компиляции:
Так программа работает ожидаемым образом:
Важно отметить, что мы можем передать ноль, один или несколько аргументов в дополнение к срезу, который мы раскрываем. Вот так будет выглядеть код, передающий все варианты, которые мы видели до этого:
Теперь мы знаем, как передавать в функцию с переменным количеством аргументов ноль, один или несколько аргументов, а также раскрываемый срез.
Заключение
В этой статье мы показали, как можно использовать функции с переменным количеством аргументов, чтобы сделать код более удобочитаемым. Хотя и требуются не всегда, но они могут оказаться для вас полезными:
Чтобы узнать больше о создании и вызове функций, вы можете прочитать материал Определение и вызов функций в Go.