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)) }
在代码示例中,我们有两个函数:add
和sub
。对于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) {
我们有三个命名的返回值:a
、b
和c
。
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) }
在代码示例中,x
和User
结构的原始值没有被修改。
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
函数将inc
和dec
函数作为参数。
func apply(x int, f func(int) int) int {
我们指定第二个参数是一个函数类型。
r1 := apply(3, inc) r2 := apply(2, dec)
我们将inc
和dec
函数作为参数传递给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教程。