Go: часть 9 – функции

Предыдущая часть – часть 8 – циклы.

Функция – это группа операторов, которые вместе выполняют определённую задачу. В каждой программе Go имеется как имнимум одна функция, которая называется main(). Кроме неё – вы можете разбить код на другие функции по вашему усмотрению, но логически каждая функция должна выполнять определённую задачу.

Объявление (declaration) функции указывает компилятору имя функции, тип возвращаемого значения (или значений) и её аргументы. Описание (definition) функции включает в себя непоследственно тело фнукции.

Стандартная библиотека Go предоставляет большой набор встроенных функций, которые вы можете использовать в своих программах, например – функция len() принимает объект в виде аргумента и возвращет длину элементов в этом объекте:

package main

import "fmt"

func main() {

    var a [5]int
    fmt.Printf("len array called a: %d\n", len(a));
}

Результат:

[simterm]

$ go run len.go
len array called a: 5

[/simterm]

Описание функции

Синтаксис описания функции:

func function_name( [parameter list] ) [return_types]
{
   body of the function
}

Описание состоит из заголовка функции и тела фукнции.

Остальные части из примера синтаксиса выше:

  • func – объявление о начале описания функции
  • function_name – имя фукнции
  • parameter list – список параметров функции. При вызове функции и передаче ей параметров – в параметре указыавется значение, которое называется аргументом функции. В списке параметров указывается тип принимаемого аргумента, порядок параметров, и их количество. Параметры функции являются опциональными, т.е. функция может не иметь параметров вообще.
  • return_types – возврщаемое значение или значения, в которых указываются типы данных, которые возвращает функция. Функция может не возвращать никаких значени й вообще, в таком случае return_types можно не указывать.
  • body of the function – тело функции, в котором указываются операторы и выражения для выполнения операции

Пример

В следующем коде демонстрируется функция с именем max(), которая принимает два параметра – num1 и num2, и возвращает большее из них:

/* function returning the max between two numbers */
func max(num1, num2 int) int {

   /* local variable declaration */
   result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

Вызов функции

Когда вы создаёте функцию – вы указывает её описание, и что она будет делать. Для её использования – вам необходимо вызвать эту функцию.

Когда программа вызывает функцию – она передаёт управление выполнением программы этой функции: функция выполняет необходимую задачу, и когда она достигает оператора return или замыкающей фигурной скобки – она передаёт контроль выполнения обратно программме.

Для вызова функции вы просто указываете имя функции и необходимые параметры. Если функция возвращает какое-либо значение – вы можете сохранить его в переменную:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
 
    // pass two args to a, b vartiables, convert them to integer
    a, _ := strconv.Atoi(os.Args[1])
    b, _ := strconv.Atoi(os.Args[2])

    // define variable to return
    var ret int 

    // calling a function to get max value
    ret = max(a, b)

    fmt.Printf( "Max value is : %d\n", ret )
}

/* function returning the max between two numbers */
func max(num1, num2 int) int {

    // local variable declaration
    var result int 

    if (num1 > num2) {
       result = num1
    } else {
       result = num2
    }   
    return result
}

Результат выполнения этой программы:

[simterm]

$ go run max.go 100 200
Max value is : 200

[/simterm]

Возврат нескольких значений

В Go функция может вернуть не одно, а два и более значений, например:

package main

import (
    "fmt"
    "os"
)

func swap(x, y string) (string, string) {
    return y, x
}

func main() {

    var a string = os.Args[1]
    var b string = os.Args[2]

    c, d := swap(a, b)
    fmt.Println(c, d)
}

Результат:

[simterm]

$ go run mult.go one two
two one

[/simterm]

Аргументы функции

Когда функция использует аргументы – в её заголовке должны быть указаны типы переменных, которые будут принимать эти аргументы. Такие переменные называются формальные параметрами (formal parameter) функции.

Формальные параметры ведут себя в функции как обычные локальные фукнции переменные, создаются при начале выполнения функции и удаляются при её завершении.

При вызове функции имеется два способа передачи аргументов:

Sr.No Call Type & Description
1 Call by value

Вызов по значению – этот способ выполняет копирование непосредственно значения аргумента в формальный параметр функции, т.е. значение запсиывается в участок памяти, выделенный под переменную, которая является формальным аргументом функции. В таком случае изменения, сделанные внутри функции, не повлияют на значение аргумента.

2 Call by reference

Вызов по ссылке – этот методе копирует адрес участка памяти, который содержит значение аргумента в формальный параметр. Внутри функции этот адрес используется для обработки и изменения значения.

 

Вызов по значению

По умолчанию в Go используется этот метод передачи данных в функцию, что означает, что функция не может менять значение аругмента.

Например, функция swap():

/* function definition to swap the values */
func swap(int x, int y) int {
   var temp int

   temp = x /* save the value of x */
   x = y    /* put y into x */
   y = temp /* put temp into y */

   return temp;
}

Теперь – вызовем её, и передадим ей значения из main():

package main

import "fmt"

func main() {

   // local variable definition
   var a int = 100
   var b int = 200

   fmt.Printf("Before swap, value of a : %d\n", a )
   fmt.Printf("Before swap, value of b : %d\n", b )

   // calling a function to swap the values
   swap(a, b)

   fmt.Printf("After swap, value of a : %d\n", a )
   fmt.Printf("After swap, value of b : %d\n", b )
}
func swap(x, y int) int {

   var temp int

   temp = x // save the value of x
   x = y    // put y into x
   y = temp // put temp into y

   return temp;
}

И результат выполнения:

[simterm]

$ go run swap.go
Before swap, value of a : 100
Before swap, value of b : 200
After swap, value of a : 100
After swap, value of b : 200

[/simterm]

Мы видим, что хотя внутри функции swap() значения были поменяны местами – их реальные значения вне функции остались неизменными.

Вызов по ссылке

Примечание: указатели ещё не рассматривались, можно посмотреть в посте C: указатели – подробный разбор

Для передачи значения по ссылке – функции передаётся указатель аргумента. Соответсвенно, при описании функции в её  заголовке вы должны указать тип переменных параметров как указатель.

Например:

/* function definition to swap the values */
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* save the value at address x */
   *x = *y      /* put y into x */
   *y = temp    /* put temp into y */
}

Теперь давайте вызовем swap(), передав значения по указателю:

package main

import "fmt"

func main() {

   // local variable definition 
   var a int = 100 
   var b int = 200 

   fmt.Printf("Before swap, value of a : %d\n", a ) 
   fmt.Printf("Before swap, value of b : %d\n", b ) 

   /* calling a function to swap the values.
   * &a indicates pointer to a ie. address of variable a and 
   * &b indicates pointer to b ie. address of variable b.
   */
   swap(&a, &b) 

   fmt.Printf("After swap, value of a : %d\n", a ) 
   fmt.Printf("After swap, value of b : %d\n", b ) 
}

func swap(x *int, y *int) {

   var temp int 
   temp = *x // save the value at address x
   *x = *y // put y into x
   *y = temp // put temp into y
}

И результат:

[simterm]

$ go run swap_rf.go
Before swap, value of a : 100
Before swap, value of b : 200
After swap, value of a : 200
After swap, value of b : 100

[/simterm]

Теперь видно, что изменения в функции затронули и значения вне её, в отличии от использования вызова по значению.

Применение функций

Функции можно применять следующими способами:

Sr.No Function Usage & Description
1 Function as ValueФункция создаётся “на лету”, и используется как значение.
2 Function ClosuresЗамыкания – анонимные функции в динамическом программировании
3 MethodМетоды – специальные функции с получателями (receiver)

Функция как значение

В Go предоставляется возможность создавать функции на лету и использовать их как значения.

В следующем примере мы инициализируем переменную с описанием функции, чья роль – просто использовать встроенную функцию math.sqrt():

package main

import (
    "fmt"
    "math"
)

func main(){

   // declare a function variable
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   // use the function
   fmt.Println(getSquareRoot(9))
}

Результат:

[simterm]

$ go run func_val.go
3

[/simterm]

Замыкания

В Go поддерживаются анонимные функции, которые работают как функции замыкания. Анонимные функции используются когда мы хотим создать функцию без объявления её имени.

В следующем примере мы создадим функцию getSequence(), которая возвращает другую функцию. Роль этой функции – использовать внешнюю ей переменную i вышестоящей функции, что бы сформировать замыкание:

 

package main

import "fmt"

func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
      return i  
   }
}

func main(){

   // nextNumber is now a function with i as 0
   nextNumber := getSequence()  

   // invoke nextNumber to increase i by 1 and return the same
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   // create a new sequence and see the result, i is 0 again
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

Результат:

[simterm]

$ go run func_clos.go
1
2
3
1
2

[/simterm]

Методы

Go поддерживает специальные типы функций, называемые методами. В объявлении методов присутсвует “получатель”, который представляет собой контейнер функции. Этот получатель может быть использован, что бы вызывать функцию используя оператор “.“.

Синтаксис:

func (variable_name variable_data_type) function_name() [return_type]{
   /* function body*/
}

Пример:

package main
  
import (
   "fmt" 
   "math" 
)

// define a circle 
type Circle struct {
   x,y,radius float64
}

// define a method for circle
func(circle Circle) area() float64 {
   return math.Pi * circle.radius * circle.radius
}

func main(){
   circle := Circle{x:0, y:0, radius:5}
   fmt.Printf("Circle area: %f\n", circle.area())
}

[simterm]

$ go run func_meth.go
Circle area: 78.539816

[/simterm]

Продолжение.