Создание функций
В Go функции позволяют объединять набор инструкций, которые можно вызывать из других частей приложения. Можно не создавать программу с множеством инструкций, а использовать функции для организации кода и повышения его удобочитаемости. Более удобочитаемый код значительно проще в обслуживании.
До этого момента мы вызывали функцию fmt.Println()
и писали код в функции main()
. В этом разделе вы узнаете, как создавать пользовательские функции. Вы также изучите некоторые другие методы, которые можно использовать с функциями в Go.
Основная функция
Функция, с которой вы взаимодействовали, — это функция main()
. Она содержится во всех исполняемых программах в Go, так как является их начальной точкой. В программе может быть только одна функция main()
. При создании пакета Go писать функцию main()
не нужно. Процесс создания пакетов будет рассматриваться в последующем модуле.
Прежде чем перейти к основам создания пользовательских функций в Go, уделим время одному из важнейших аспектов функции main()
. Как вы могли заметить, у функции main()
нет параметров и она ничего не возвращает. Но это не значит, что она не может считывать введенные пользователем значения, например аргументы командной строки. Чтобы получить доступ к аргументам командной строки в Go, можно воспользоваться пакетом os и переменной os.Args
, содержащей все аргументы, переданные программе.
Следующий код считывает два числа из командной строки и суммирует их:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
number1, _ := strconv.Atoi(os.Args[1])
number2, _ := strconv.Atoi(os.Args[2])
fmt.Println("Sum:", number1+number2)
}
Переменная os.Args
содержит все аргументы командной строки, переданные программе. Так как эти значения имеют тип string
, их необходимо преобразовать в int
, чтобы суммировать.
Запустите программу с помощью следующей команды:
go run main.go 3 5
Появятся следующие выходные данные.
Sum: 8
Теперь посмотрим, как можно выполнить рефакторинг приведенного выше кода, и создадим первую пользовательскую функцию.
Пользовательские функции
Ниже приведен синтаксис для создания функции.
func name(parameters) (results) {
body-content
}
Обратите внимание, что для определения функции и последующего назначения ей имени используется ключевое слово func
. После имени нужно указать список параметров для функции. Параметров может быть несколько или ни одного. Можно также определить типы возвращаемых данных функции. Этих типов может быть несколько или ни одного. (Мы поговорим о возврате нескольких значений в следующем разделе.) После определения всех этих значений вы записываете содержимое функции в текст.
Чтобы попрактиковаться в использовании этого метода, выполним рефакторинг кода из предыдущего раздела для суммирования чисел в пользовательской функции. Будем использовать этот код:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
sum := sum(os.Args[1], os.Args[2])
fmt.Println("Sum:", sum)
}
func sum(number1 string, number2 string) int {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
return int1 + int2
}
Код создает функцию с именем sum
, которая принимает два аргумента string
, приводит их к int
, а затем возвращает результат их суммирования. При определении типа возвращаемого значения функция должна возвращать значение этого типа.
В Go можно также задать имя возвращаемого значения функции так, как если бы это была переменная. Например, можно выполнить рефакторинг функции sum
следующим образом:
func sum(number1 string, number2 string) (result int) {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
result = int1 + int2
return
}
Обратите внимание, что теперь необходимо заключить результирующее значение функции в круглые скобки. Вы также можете использовать переменную внутри функции и просто добавить return
строку в конце. Go вернет текущие значения этих возвращаемых переменных. Простота написания return
ключевого слова в конце функции является привлекательной (особенно при наличии нескольких возвращаемых значений). Этот вариант использовать не рекомендуется. Может быть неясно, что возвращает функция.
Возврат нескольких значений
В Go функция может возвращать несколько значений. Их можно определить способом, аналогичным определению параметров функции. Другими словами, вы указываете тип и имя, но имя является необязательным.
Предположим, что необходимо создать функцию, которая не только суммирует, но и умножает два числа. Код функции будет выглядеть следующим образом:
func calc(number1 string, number2 string) (sum int, mul int) {
int1, _ := strconv.Atoi(number1)
int2, _ := strconv.Atoi(number2)
sum = int1 + int2
mul = int1 * int2
return
}
Теперь для хранения результатов функции требуются две переменные. (В противном случае он не будет компилироваться.) Вот как выглядит:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
sum, mul := calc(os.Args[1], os.Args[2])
fmt.Println("Sum:", sum)
fmt.Println("Mul:", mul)
}
Еще одна интересная особенность Go заключается в том, что если одно из возвращаемых значений функции не требуется, его можно отменить, назначив возвращаемое значение переменной _
. _
переменная является идиоматическим вариантом игнорирования в Go возвращаемых значений. В этом случае программа будет компилироваться. Поэтому, если требуется только значение суммы, можно использовать следующий код:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
sum, _ := calc(os.Args[1], os.Args[2])
fmt.Println("Sum:", sum)
}
Игнорирование возвращаемых значений из функций будет рассматриваться более подробно при изучении обработки ошибок в последующем модуле.
Изменение значений параметров функции (указателей)
При передаче значения функции каждое изменение этой функции не затрагивает вызывающий объект. Go — это язык программирования с передачей по значению. При передаче значения функции Go принимает это значение и создает локальную копию (новую переменную в памяти). Изменения, вносимые в эту переменную в функции, не влияют на ту, которая была отправлена в функцию.
Предположим, что вы создаете функцию для обновления имени человека. Обратите внимание на то, что происходит при выполнении этого кода:
package main
import "fmt"
func main() {
firstName := "John"
updateName(firstName)
fmt.Println(firstName)
}
func updateName(name string) {
name = "David"
}
Даже несмотря на то, что имя в функции было изменено на "Давид", результатом по-прежнему будет "Джон". Выходные данные не изменились, так как изменение в функции updateName
изменяет только локальную копию. Go передает значение переменной, а не саму переменную.
Если требуется, чтобы изменения, вносимые в функцию updateName
, влияли на переменную firstName
в функции main
, необходимо использовать указатель. Указатель — это переменная, которая хранит адрес памяти другой переменной. При отправке указателя на функцию вы не передаете значение, передаете адрес памяти. Таким образом, каждое изменение, вносимое в эту переменную, влияет на вызывающий объект.
В Go есть два оператора для работы с указателями:
- Оператор
&
принимает адрес объекта, следующего за ним. - Оператор
*
разыменовывает указатель. Он предоставляет доступ к объекту по адресу, указанному в указателе.
Давайте изменим предыдущий пример и посмотрим, как работают указатели:
package main
import "fmt"
func main() {
firstName := "John"
updateName(&firstName)
fmt.Println(firstName)
}
func updateName(name *string) {
*name = "David"
}
Выполните предыдущий код. Обратите внимание, что теперь в выходных данных отображается David
вместо John
.
Прежде всего, необходимо изменить сигнатуру функции, чтобы указать, что вы хотите получить указатель. Для этого измените тип параметра с string
на *string
. (Последний по-прежнему является строкой, но теперь это указатель на строку.) Затем при назначении нового значения этой переменной необходимо добавить звездочку (*
) слева от переменной, чтобы получить значение этой переменной. При вызове функции updateName
отправляется не значение, а адрес памяти переменной. Символ &
слева от переменной указывает адрес переменной.