开放的编程资料库

当前位置:我爱分享网 > Go教程 > 正文

Go函数

Go函数教程展示了如何在Golang中使用函数。

Go函数定义

函数是零个或多个输入参数到零个或多个输出参数的映射。

使用函数的优点是:

  • 减少代码重复
  • 将复杂问题分解成更简单的部分
  • 提高代码的清晰度
  • 代码重用
  • 信息隐藏

Go函数是一等公民。函数可以分配给变量,作为参数传递给函数或从函数返回。这使得语言更加灵活。

Go中的函数是使用func关键字创建的。我们使用return关键字从函数返回值。函数体由调用函数时执行的语句组成。主体由一对花括号{}分隔。要调用函数,我们指定其名称后跟圆括号()。函数可以带参数也可以不带参数。

$ go version
go version go1.18.1 linux/amd64

我们使用Go版本1.18。

Go函数简单示例

以下示例在Go中创建了一个简单的函数。

package main

import "fmt"

func main() {

    x := 4
    y := 5

    z := add(x, y)

    fmt.Printf("Output: %d\n", z)
}

func add(a int, b int) int {

    return a + b
}

在代码示例中,我们定义了一个将两个值相加的函数。

z := add(x, y)

我们调用add函数;它需要两个参数。计算值传递给z变量。

func add(a int, b int) int {

    return a + b
}

我们定义了add函数。函数的参数用逗号分隔;每个参数名称后跟它的数据类型。在参数之后,我们指定返回值类型。调用函数时执行的语句放在花括号中。加法运算的结果用return关键字返回给调用者。

$ go run simple.go 
Output: 9

Go函数省略类型

当函数的参数类型相同时,可以省略部分参数的类型;也就是说,只指定一次。

package main

import "fmt"

func add(x int, y int) int {

    return x + y
}

func sub(x, y int) int {

    return x - y
}

func main() {

    fmt.Println(add(5, 4))
    fmt.Println(sub(5, 4))
}

在代码示例中,我们有两个函数:addsub。对于sub函数,省略了x变量的类型。

Go函数命名返回变量

我们可以在函数参数后的圆括号中指定命名的返回变量。

package main

import "fmt"

func inc(x, y, z int) (a, b, c int) {

    a = x + 1
    b = y + 1
    c = z + 1

    return
}

func main() {

    x, y, z := inc(10, 100, 1000)

    fmt.Println(x, y, z)
}

在代码示例中,我们有一个递增其三个参数的函数。

func inc(x, y, z int) (a, b, c int) {

我们有三个命名的返回值:abc

a = x + 1
b = y + 1
c = z + 1

return

我们计算返回变量的值。之后,我们必须指定return关键字。

$ go run namedretvals.go 
11 101 1001

Go函数多返回值

Go函数允许返回多个值。

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func threerandom() (int, int, int) {

    rand.Seed(time.Now().UnixNano())
    x := rand.Intn(10)
    y := rand.Intn(10)
    z := rand.Intn(10)

    return x, y, z
}

func main() {

    r1, r2, r3 := threerandom()

    fmt.Println(r1, r2, r3)
}

在代码示例中,我们有一个threerandom函数,它返回三个随机值。

func threerandom() (int, int, int) {

我们指定该函数返回三个整数值。

rand.Seed(time.Now().UnixNano())
x := rand.Intn(10)
y := rand.Intn(10)
z := rand.Intn(10)

我们计算三个随机值。

return x, y, z

值是从函数返回的。它们以逗号分隔。

$ go run multiple.go 
0 8 0

去匿名函数

我们可以创建匿名函数。匿名函数没有名称。

package main

import "fmt"

func main() {

    sum := func(a, b, c int) int {
        return a + b + c
    }(3, 5, 7)

    fmt.Println("5+3+7 =", sum)
}

我们创建了一个添加三个值的匿名函数。我们在函数定义后立即将三个参数传递给该函数。

$ go run anonymous.go 
5+3+7 = 15

Go可变函数

可变参数函数可以接受可变数量的参数。例如,当我们想要计算值的总和时,我们可能有四个、五个、六个等值要传递给函数。

我们使用...(省略号)运算符来定义可变参数函数。

package main

import "fmt"

func main() {

    s1 := sum(1, 2, 3)
    s2 := sum(1, 2, 3, 4)
    s3 := sum(1, 2, 3, 4, 5)

    fmt.Println(s1, s2, s3)
}

func sum(nums ...int) int {

    res := 0

    for _, n := range nums {
        res += n
    }

    return res
}

在代码示例中,我们有一个sum函数,它接受可变数量的参数。

func sum(nums ...int) int {

    res := 0

    for _, n := range nums {
        res += n
    }

    return res
}

nums变量是一个切片,它包含传递给sum函数的所有值。我们遍历切片并计算参数的总和。

$ go run variadic.go 
6 10 15

Go递归函数

递归,在数学和计算机科学中,是一种定义方法的方式,其中被定义的方法在其自己的定义中应用。换句话说,递归方法调用自身来完成它的任务。递归是一种广泛用于解决许多编程任务的方法。

一个典型的例子是阶乘的计算。

package main

import "fmt"

func fact(n int) int {

    if n == 0 || n == 1 {
        return 1
    }

    return n * fact(n-1)
}

func main() {

    fmt.Println(fact(7))
    fmt.Println(fact(10))
    fmt.Println(fact(15))
}

在此代码示例中,我们计算三个数字的阶乘。

return n * fact(n-1)

fact函数的主体内,我们使用修改后的参数调用fact函数。该函数调用自身。

$ go run factorial.go 
5040
3628800
1307674368000

这些是计算出的阶乘。

去延迟函数调用

defer语句推迟函数的执行,直到周围的函数返回。延迟调用的参数会立即求值,但直到周围函数返回时才会执行函数调用。

package main

import "fmt"

func main() {

    fmt.Println("begin main")

    defer sayHello()

    fmt.Println("end main")
}

func sayHello() {

    fmt.Println("hello")
}

在代码示例中,sayHello函数在main函数完成后被调用。

$ go run defercall.go 
begin main
end main
hello

按值传递参数

在Go中,函数的参数仅按值传递。

在下面的例子中,一个整数和一个User结构被作为参数传递给函数。

package main

import "fmt"

type User struct {
    name       string
    occupation string
}

func main() {

    x := 10
    fmt.Printf("inside main %d\n", x)

    inc(x)

    fmt.Printf("inside main %d\n", x)

    fmt.Println("---------------------")

    u := User{"John Doe", "gardener"}
    fmt.Printf("inside main %v\n", u)

    change(u)
    fmt.Printf("inside main %v\n", u)
}

func inc(x int) {

    x++
    fmt.Printf("inside inc %d\n", x)
}

func change(u User) {

    u.occupation = "driver"
    fmt.Printf("inside change %v\n", u)
}

在代码示例中,xUser结构的原始值没有被修改。

func inc(x int) {

    x++
    fmt.Printf("inside inc %d\n", x)
}

创建了整数值的副本。在函数内部,我们增加这个副本的值。所以原始变量是完整的。

$ go run callbyval.go 
inside main 10
inside inc 11
inside main 10
---------------------
inside main {John Doe gardener}
inside change {John Doe driver}
inside main {John Doe gardener}

在下一个示例中,我们将指针传递给整数变量和结构。

package main

import "fmt"

type User struct {
    name       string
    occupation string
}

func main() {

    x := 10
    fmt.Printf("inside main %d\n", x)

    inc(&x)

    fmt.Printf("inside main %d\n", x)

    fmt.Println("---------------------")

    u := User{"John Doe", "gardener"}
    fmt.Printf("inside main %v\n", u)

    change(&u)
    fmt.Printf("inside main %v\n", u)
}

func inc(x *int) {

    (*x)++
    fmt.Printf("inside inc %d\n", *x)
}

func change(u *User) {

    u.occupation = "driver"
    fmt.Printf("inside change %v\n", *u)
}

现在修改了原始值。但从技术上讲,参数仍然是按值传递的。Go创建指针的新副本。(这与C不同。)

inc(&x)

通过&字符,我们将指针传递给x变量。

func inc(x *int) {

    (*x)++
    fmt.Printf("inside inc %d\n", *x)
}

创建指向x变量的指针的副本。更改x的值也会修改原始变量。

$ go run callbyval2.go 
inside main 10
inside inc 11
inside main 11
---------------------
inside main {John Doe gardener}
inside change {John Doe driver}
inside main {John Doe driver}

原始值已被修改。

数组是值类型,切片和映射是引用类型。因此,对于切片和映射,会创建引用的副本。

package main

import "fmt"

func main() {

    vals := []int{1, 2, 3, 4, 5}

    fmt.Printf("%v\n", vals)

    square(vals)

    fmt.Printf("%v\n", vals)
}

func square(vals []int) {

    for i, val := range vals {

        vals[i] = val * val
    }
}

在代码示例中,我们将切片传递给square函数。原始切片的元素被修改。

$ go run passslice.go 
[1 2 3 4 5]
[1 4 9 16 25]

元素是平方的。

数组是值类型。

package main

import "fmt"

func main() {

    vals := [5]int{1, 2, 3, 4, 5}

    fmt.Printf("%v\n", vals)

    square(vals)

    fmt.Printf("%v\n", vals)
}

func square(vals [5]int) {

    for i, val := range vals {

        vals[i] = val * val
    }
}

该示例将一个数组传递给square函数。

$ go run passarray.go 
[1 2 3 4 5]
[1 2 3 4 5]

元素没有被修改。

地图是引用类型。

package main

import "fmt"

func main() {

    items := map[string]int{"coins": 1, "pens": 2, "chairs": 4}
    fmt.Printf("%v\n", items)

    update(items)

    fmt.Printf("%v\n", items)
}

func update(items map[string]int) {

    items["coins"] = 6
}

该示例将映射传递给update函数。

$ go run passmap.go 
map[chairs:4 coins:1 pens:2]
map[chairs:4 coins:6 pens:2]

可以看到,原来的地图已经更新了。

Go函数作为参数

Go函数可以作为参数传递给其他函数。这样的函数称为高阶函数。

package main

import "fmt"

func inc(x int) int {
    x++
    return x
}

func dec(x int) int {
    x--
    return x
}

func apply(x int, f func(int) int) int {

    r := f(x)
    return r
}

func main() {
    r1 := apply(3, inc)
    r2 := apply(2, dec)
    fmt.Println(r1)
    fmt.Println(r2)
}

在代码示例中,apply函数将incdec函数作为参数。

func apply(x int, f func(int) int) int {

我们指定第二个参数是一个函数类型。

r1 := apply(3, inc)
r2 := apply(2, dec)

我们将incdec函数作为参数传递给apply函数。

$ go run funaspar.go 
4
1

Go自定义函数类型

Go允许使用type关键字创建可重用的函数签名。

package main

import "fmt"

type output func(string) string

func hello(name string) string {

    return fmt.Sprintf("hello %s", name)
}

func main() {
    var f output

    f = hello
    fmt.Println(f("Peter"))
}

使用type关键字,我们创建了一个函数类型,它接受一个字符串参数并返回一个字符串。

去过滤函数

我们有一个过滤数据的实际例子。

package main

import "fmt"

type User struct {
    name       string
    occupation string
    married    bool
}

func main() {

    u1 := User{"John Doe", "gardener", false}
    u2 := User{"Richard Roe", "driver", true}
    u3 := User{"Bob Martin", "teacher", true}
    u4 := User{"Lucy Smith", "accountant", false}
    u5 := User{"James Brown", "teacher", true}

    users := []User{u1, u2, u3, u4, u5}

    married := filter(users, func(u User) bool {
        if u.married == true {
            return true
        }
        return false
    })

    teachers := filter(users, func(u User) bool {

        if u.occupation == "teacher" {
            return true
        }
        return false
    })

    fmt.Println("Married:")
    fmt.Printf("%v\n", married)

    fmt.Println("Teachers:")
    fmt.Printf("%v\n", teachers)

}

func filter(s []User, f func(User) bool) []User {
    var res []User

    for _, v := range s {

        if f(v) == true {
            res = append(res, v)
        }
    }
    return res
}

我们有一段User结构。我们过滤切片以形成已婚用户和教师用户的新切片。

married := filter(users, func(u User) bool {
    if u.married == true {
        return true
    }
    return false
})

我们调用filter函数。它接受一个匿名函数作为参数。该函数为已婚用户返回true。返回布尔值的函数也称为谓词

func filter(s []User, f func(User) bool) []User {
    var res []User

    for _, v := range s {

        if f(v) == true {
            res = append(res, v)
        }
    }
    return res
}

filter函数为所有满足给定条件的用户形成一个新切片。

$ go run filtering.go 
Married:
[{Richard Roe driver true} {Bob Martin teacher true} {James Brown teacher true}]
Teachers:
[{Bob Martin teacher true} {James Brown teacher true}]

去闭包

Go中的闭包是从封闭函数返回的匿名函数。闭包保留对其主体外定义的变量的引用。

package main

import "fmt"

func intSeq() func() int {

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

func main() {

    nextInt := intSeq()

    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    nextInt2 := intSeq()
    fmt.Println(nextInt2())
}

我们有intSeq函数,它生成一个整数序列。它返回一个递增i变量的闭包。

func intSeq() func() int {

intSeq是一个返回整数的函数。

func intSeq() func() int {

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

函数中定义的变量具有局部函数作用域。但是,在这种情况下,即使在intSeq函数返回之后,闭包也绑定到i变量。

nextInt := intSeq()

我们调用intSeq函数。它返回一个函数,该函数将增加一个计数器。闭包存储在nextInt变量中。

fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())

我们多次调用闭包。

$ go run closure.go 
1
2
3
4
1

转到高阶函数

高阶函数是接受函数作为参数或返回函数的函数。

package main

import "fmt"

func main() {

    x := 3
    y := 4

    add, sub := getAddSub()

    r1, r2 := apply(x, y, add, sub)

    fmt.Printf("%d + %d = %d\n", x, y, r1)
    fmt.Printf("%d - %d = %d\n", x, y, r2)
}

func apply(x, y int, add func(int, int) int, sub func(int, int) int) (int, int) {

    r1 := add(x, y)
    r2 := sub(x, y)

    return r1, r2
}

func getAddSub() (func(int, int) int, func(int, int) int) {

    add := func(x, y int) int {
        return x + y
    }

    sub := func(x, y int) int {
        return x - y
    }

    return add, sub
}

在代码示例中,我们有一些复杂的函数操作。apply函数将两个函数作为参数。getAddSub函数返回两个函数。

$ go run higherorder.go 
3 + 4 = 7
3 - 4 = -1

在本教程中,我们介绍了Golang中的函数。

列出所有Go教程。

未经允许不得转载:我爱分享网 » Go函数

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏