Go语言入门系列(六)之再探函数
行人观学 · · 1037 次点击 · · 开始浏览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传入指针能够真正交换两个变量的值。
传入指针的好处:
- 传入指针使我们能够在函数中直接操作变量,多个函数也能操作同一个变量。
- 不需要再拷贝一遍值了。如果你需要传入比较大的结构体,再拷贝一遍就多花费系统开销了,而传入指针则小的多。
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类型的函数sum和product,所以可以向choose函数中传入这两个函数。
我们给变量diff赋了一个函数,所以能够使用diff(9, 8),或者将其作为参数传入choose函数。
作者简介
我是「行小观」,于千万人中的一个普通人。阴差阳错地走上了编程这条路,既然走上了这条路,那么我会尽可能远地走下去。我会在公众号『行人观学』中持续更新「Java」、「Go」、「数据结构和算法」、「计算机基础」等相关文章。
欢迎关注,我们一起踏上行程。
本文章属于系列文章「Go语言入门系列」。
如有错误,还请指正。
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
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传入指针能够真正交换两个变量的值。
传入指针的好处:
- 传入指针使我们能够在函数中直接操作变量,多个函数也能操作同一个变量。
- 不需要再拷贝一遍值了。如果你需要传入比较大的结构体,再拷贝一遍就多花费系统开销了,而传入指针则小的多。
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类型的函数sum和product,所以可以向choose函数中传入这两个函数。
我们给变量diff赋了一个函数,所以能够使用diff(9, 8),或者将其作为参数传入choose函数。
作者简介
我是「行小观」,于千万人中的一个普通人。阴差阳错地走上了编程这条路,既然走上了这条路,那么我会尽可能远地走下去。我会在公众号『行人观学』中持续更新「Java」、「Go」、「数据结构和算法」、「计算机基础」等相关文章。
欢迎关注,我们一起踏上行程。
本文章属于系列文章「Go语言入门系列」。
如有错误,还请指正。