乐趣区

关于编程语言:Go语言入门系列六之再探函数

Go 语言入门系列后面的文章:

  • Go 语言入门系列 (三) 之数组和切片
  • Go 语言入门系列 (四) 之 map 的应用
  • Go 语言入门系列 (五) 之指针和构造体的应用

在 Go 语言入门系列 (二) 之根底语法总结这篇文章中曾经介绍过了 Go 语言的函数的根本应用,包含申明、参数、返回值。本文再具体介绍一下函数的其余应用。

1. 变参

Go 语言的函数除了反对 0 个或多个参数,还反对不定数量的参数,即变参。申明形式为:

func foo(变参名 ... 参数类型) 函数类型 {// 函数体}

上面是一个具体的函数,它接管不定数量的 int 参数,并返回和:

package main

import "fmt"

func add(arg ...int) int { // 变参函数
    var sum int
    for _, value := range arg {sum += value}
    return sum
}

func main() {sum := add(1, 2, 3, 4)
    fmt.Println(sum) //10
}

arg ...int表明 add 函数接管不定数量的参数,且只能是 int 类型的。arg是咱们给该变参取的名字,它实际上是一个切片,所以在 add 函数中能够应用 range 遍历变量arg

2. 传值和传指针

当咱们调用一个有参函数时,必定会向该函数中传入参数:

package main

import "fmt"

// 传入一个值,打印它
func printX(x int)  {fmt.Println(x)
}

func main() {
    var a int = 5
    printX(a) // 向函数中传入参数:变量 a
}

这里有一个问题:咱们真的是把变量 a 传给了 printX 函数吗?咱们用两个例子来阐明问题。

2.1. 例 1

package main

import "fmt"

func plusOne(x int) int {
    x = x + 1
    fmt.Println("执行加一")
    return x
}

func main() {
    a := 5
    fmt.Println("a =", a) // 应该为 5 理论为 5
    b := plusOne(a)
    fmt.Println("a =", a) // 应该为 6 理论为 5
    fmt.Println("b =", b) // 应该为 6 理论为 6
}

plusOne函数的作用是把传进来的参数加一,并返回后果。

a=5传进 plusOne 函数,执行了 x = x + 1 语句,那么执行过后 a 的值应该为 6,但理论为 5。

变量 b 接管了函数的返回值,所以为 6,这没问题。

这证实了,咱们的 a 变量基本就没传进函数中,那么理论传的是什么?理论传的是 a 变量的一份拷贝。

所以,咱们向 Go 语言中的函数传入一个值,实际上传的是该值的拷贝,而非该值自身。

那如果咱们的确要把上例中的变量 a 传入 plusOne 函数中呢?那此时就不应该传值了,而是 应该传入指针。代码改良如下:

package main

import "fmt"

func plusOne(x *int) int { // 参数是指针变量
    *x = *x + 1
    fmt.Println("执行加一")
    return *x
}

func main() {
    a := 5
    fmt.Println("a =", a) // 应该为 5 理论为 5
    b := plusOne(&a) // 传入地址
    fmt.Println("a =", a) // 应该为 6 理论为 6
    fmt.Println("b =", b) // 应该为 6 理论为 6
}

a=5传进 plusOne 函数,执行了 x = x + 1 语句,执行过后 a 的值理论为 6。

这就证实,变量 a 的确被传进 plusOne 函数并被批改了。因为咱们传进去的是一个指针,即变量的地址,有了地址咱们能够间接操作变量。

如果你对指针的应用不相熟,这里的代码可能会有点难了解,上面逐行解释:

func plusOne(x *int) int {

申明 x 是一个 int 类型的指针参数,只承受 int 类型变量的地址。

*x = *x + 1

应用 * 操作符依据 x 中存的地址,获取到对应的值,而后加一。

return *x

应用 * 操作符依据 x 中存的地址,获取到对应的值,而后返回。

b := plusOne(&a) 

plusOne函数只承受 int 类型变量的地址,所以应用 & 操作符获取 a 变量的地址,而后才传入。

2.2. 例 2

上面我再举一个经典的例子:写一个函数,可能替换两个变量的值。

如果你不晓得什么是传值和传指针,那可能会写成这样:

package main

import "fmt"

func swap(x, y int) {
    tmp := x
    x = y
    y = tmp
    fmt.Println("函数中:x =", x, ", y =", y)
}

func main()  {
    x, y := 2, 8
    fmt.Println("替换前:x =", x, ", y =", y)
    swap(x, y)
    fmt.Println("替换后:x =", x, ", y =", y)
}

运行后果:

替换前:x = 2 , y = 8
函数中:x = 8 , y = 2
替换后:x = 2 , y = 8

只在函数中实现了替换,出了函数又变回原样了。

想要实现替换,就必须传入指针,而非值拷贝:

package main

import "fmt"

func swap(x, y *int) {
    tmp := *x
    *x = *y
    *y = tmp
    fmt.Println("函数中:x =", *x, ", y =", *y)
}

func main()  {
    x, y := 2, 8
    fmt.Println("替换前:x =", x, ", y =", y)
    swap(&x, &y)
    fmt.Println("替换后:x =", x, ", y =", y)
}

运行后果:

替换前:x = 2 , y = 8
函数中:x = 8 , y = 2
替换后:x = 8 , y = 2

传入指针可能真正替换两个变量的值。

传入指针的益处:

  1. 传入指针使咱们可能在函数中间接操作变量,多个函数也能操作同一个变量。
  2. 不须要再拷贝一遍值了。如果你须要传入比拟大的构造体,再拷贝一遍就多破费零碎开销了,而传入指针则小的多。

3. 函数作为值

在 Go 语言中,函数也能够作为值来传递。上面是一个例子:

package main

import "fmt"

type calculate func(int, int) int // 申明了一个函数类型

func sum(x, y int) int {return x + y}

func product(x, y int) int {return x * y}

func choose(a, b int, f calculate) int { // 函数作为参数
    return f(a, b)
}

func main(){diff := func(x, y int) int { // 函数作为值赋给 diff
        return x - y
    }

    fmt.Println(choose(2, 3, sum)) //5
    fmt.Println(choose(4, 5, product)) //20
    fmt.Println(choose(6, 7, diff)) //-1
    fmt.Println(diff(9, 8)) //1
}

函数作为值或者参数必定要有对应的类型,类型是:func(参数类型)返回值类型 。比方func(int,int) int

能够应用 type 关键字给 func(int,int) int 起个别名叫calculate,方便使用。

choose函数中申明了一个类型为 calculate 的函数参数 f,而咱们又编写了calculate 类型的函数 sumproduct,所以能够向 choose 函数中传入这两个函数。

咱们给变量 diff 赋了一个函数,所以可能应用 diff(9, 8),或者将其作为参数传入choose 函数。

作者简介

我是「行小观」,于千万人中的一个普通人。阴差阳错地走上了编程这条路,既然走上了这条路,那么我会尽可能远地走上来。

我会在公众号『行人观学』中继续更新「Java」、「Go」、「数据结构和算法」、「计算机根底」等相干文章。

欢送关注,咱们一起踏上行程。

本文章属于系列文章「Go 语言入门系列」。

如有谬误,还请斧正。

退出移动版