AI智能
改变未来

Go语言入门系列(六)之再探函数

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

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

在Go语言入门系列(二)之基础语法总结这篇文章中已经介绍过了Go语言的函数的基本使用,包括声明、参数、返回值。本文再详细介绍一下函数的其他使用。

1. 变参

Go语言的函数除了支持0个或多个参数,还支持不定数量的参数,即变参。声明方式为:

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

下面是一个具体的函数,它接收不定数量的

int

参数,并返回和:

package mainimport "fmt"func add(arg ...int) int { //变参函数var sum intfor _, 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 mainimport "fmt"//传入一个值,打印它func printX(x int)  {fmt.Println(x)}func main() {var a int = 5printX(a) //向函数中传入参数:变量a}

这里有一个问题:我们真的是把变量

a

传给了

printX

函数吗?我们用两个例子来说明问题。

2.1. 例1

package mainimport "fmt"func plusOne(x int) int {x = x + 1fmt.Println("执行加一")return x}func main() {a := 5fmt.Println("a =", a) //应该为5 实际为5b := plusOne(a)fmt.Println("a =", a) //应该为6 实际为5fmt.Println("b =", b) //应该为6 实际为6}

plusOne

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

a=5

传进

plusOne

函数,执行了

x = x + 1

语句,那么执行过后

a

的值应该为6,但实际为5。

变量

b

接收了函数的返回值,所以为6,这没问题。

这证明了,我们的

a

变量根本就没传进函数中,那么实际传的是什么?实际传的是

a

变量的一份拷贝。

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

那如果我们确实要把上例中的变量

a

传入

plusOne

函数中呢?那此时就不应该传值了,而是应该传入指针。代码改进如下:

package mainimport "fmt"func plusOne(x *int) int { //参数是指针变量*x = *x + 1fmt.Println("执行加一")return *x}func main() {a := 5fmt.Println("a =", a) //应该为5 实际为5b := plusOne(&a) //传入地址fmt.Println("a =", a) //应该为6 实际为6fmt.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 mainimport "fmt"func swap(x, y int) {tmp := xx = yy = tmpfmt.Println("函数中:x =", x, ", y =", y)}func main()  {x, y := 2, 8fmt.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 mainimport "fmt"func swap(x, y *int) {tmp := *x*x = *y*y = tmpfmt.Println("函数中:x =", *x, ", y =", *y)}func main()  {x, y := 2, 8fmt.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 mainimport "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 { //函数作为值赋给diffreturn x - y}fmt.Println(choose(2, 3, sum)) //5fmt.Println(choose(4, 5, product)) //20fmt.Println(choose(6, 7, diff)) //-1fmt.Println(diff(9, 8)) //1}

函数作为值或者参数肯定要有对应的类型,类型是:

func(参数类型)返回值类型

。比如

func(int,int) int

可以使用

type

关键字给

func(int,int) int

起个别名叫

calculate

,方便使用。

choose

函数中声明了一个类型为

calculate

的函数参数

f

,而我们又编写了

calculate

类型的函数

sum

product

,所以可以向

choose

函数中传入这两个函数。

我们给变量

diff

赋了一个函数,所以能够使用

diff(9, 8)

,或者将其作为参数传入

choose

函数。

作者简介

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

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

欢迎关注,我们一起踏上行程。

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

如有错误,还请指正。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Go语言入门系列(六)之再探函数