ch13、函数-“一等公民”
1、特性
声明格式:
func 函数名称(形参列表)(返回值列表){
函数体;
}
匿名函数:
func(parameter) returnType {}
主要特性:
- 可以有多个返回值
- 所有参数可以说都是值传递: slice, map, channel 和指针会有传引用的错觉(拷贝的是内存地址的值)
- 函数可以作为变量的值
- 函数可以作为参数和返回值
package function
import (
"fmt"
"math/rand"
"testing"
"time"
)
func returnMultiValues() (int, int) {// 可以有多个返回值
return rand.Intn(10), rand.Intn(20)
}
func timeSpent(inner func(op int) int) func(op int) int { // 参数是函数,返回值也是函数
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent: ", time.Since(start).Seconds())
return ret
}
}
func slowFunc(op int) int {
time.Sleep(time.Second * 1)
return op
}
func TestFn(t *testing.T) {
_, b := returnMultiValues()
t.Log(b)
tsSF := timeSpent(slowFunc) // 传入的是函数,返回的也是函数实例 - 函数式编程
t.Log(tsSF(10))
}
学习函数式编程,推荐书籍:《计算机程序的构造和解释》
2、函数返回值
- Go语言中可以给函数的返回值指定名称
package main
import "fmt"
func main() {
fmt.Println(sum())
}
// 给返回值指定了一个名称叫做res, return时会自动将函数体内部res作为返回值
// 其实本质就是提前定义了一个局部变量res, 在函数体中使用的res就是这个局部变量,返回的也是这个局部变量
func sum() (res int) {
res = 1 + 1
return
}
/*
output:
2
*/
- Go语言中的函数允许有多个返回值函数
package main
import "fmt"
func main() {
fmt.Println(calculate(2, 1))
}
// a, b都是int类型, 所以只需要在b后面添加int即可
func calculate(a, b int) (sum, sub int) {
sum = a + b
sub = a - b
return
}
/*
output:
3 1
*/
3、值传递和引用传递
- Go语言中
值类型
有: int系列、float系列、bool、string、数组、结构体- 值类型通常在栈中分配存储空间
- 值类型作为函数参数传递, 是拷贝传递
- 在函数体内修改值类型参数, 不会影响到函数外的值(此处对于指针、slice、map、channel类型是有差异的)
// int
package main
import "fmt"
func main() {
num := 10
change(num)
fmt.Println(num) // 10
}
func change(num int) {
num = 998
}
// array
package main
import "fmt"
func main() {
arr := [3]int{1, 3, 5}
change(arr)
fmt.Println(arr) // 1, 3, 5
}
func change(arr [3]int) {
arr[1] = 8
}
// struct
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
p := Person{"lnj", 33}
change(p)
fmt.Println(p.name) // lnj
}
func change(p Person) {
p.name = "zs"
}
- Go语言中
引用类型
有: 指针、slice、map、channel- 引用类型通常在堆中分配存储空间
- 引用类型作为函数参数传递,是引用传递(也可以理解是值传递,拷贝的是内存地址)
- 在函数体内修改引用类型参数,会影响到函数外的值(这才是主要差别)
// 指针类型
package main
import "fmt"
func main() {
num := 10
change(&num)
fmt.Println(num) // 998
}
func change(num *int) {
*num = 998
}
// 指针类型
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
p := Person{"lnj", 33}
change(&p)
fmt.Println(p.name) // zs
}
func change(p *Person) {
p.name = "zs"
}
// slice
package main
import "fmt"
func main() {
arr := []int{1, 3, 5}
change(arr)
fmt.Println(arr) // 1, 8, 5
}
func change(arr []int) {
arr[1] = 8
}
// map
package main
import "fmt"
func main() {
mp := map[string]string{"name":"lnj", "age":"33"}
change(mp)
fmt.Println(mp["name"]) // zs
}
func change(mp map[string]string) {
mp["name"] = "zs"
}
4、匿名函数
-
匿名函数也是函数的一种, 它的格式和普通函数一模一样,只不过没有名字而已
- 普通函数的函数名称是固定的, 匿名函数的函数名称是系统随机的
-
匿名函数可以定义在函数外(全局匿名函数),也可以定义在函数内(局部匿名函数), Go语言中的普通函数不能嵌套定义, 但是可以通过匿名函数来实现函数的嵌套定义
- 全局匿名函数(一般情况下很少使用全局匿名函数)
package main import "fmt" // 方式一 var a = func() { fmt.Println("hello world1") } // 方式二 var ( b = func() { fmt.Println("hello world2") } ) func main() { a() b() }
-
一般情况下很少使用全局匿名函数, 大多数情况都是使用局部匿名函数, 匿名函数可以直接调用、保存到变量、作为参数或者返回值
- 直接调用
package main
import "fmt"
func main() {
func(s string) {
fmt.Println(s)
}("hello lnj")
}
- 保存到变量
package main
import "fmt"
func main() {
a := func(s string) {
fmt.Println(s)
}
a("hello lnj")
}
- 作为参数
package main
import "fmt"
func main() {
test(func(s string) {
fmt.Println(s)
})
}
func test(f func(s string)) {
f("hello lnj")
}
- 作为返回值
package main
import "fmt"
func main() {
res := test()
res(10, 20)
}
func test() func(int, int) {
return func(a int, b int) {
fmt.Println(a + b)
}
}
- 匿名函数应用场景
- 当某个函数只需要被调用一次时, 可以使用匿名函数
- 需要执行一些不确定的操作时,可以使用匿名函数
5、闭包
- 闭包是一个特殊的匿名函数, 它是匿名函数和相关引用环境组成的一个整体
- 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包
package main
import "fmt"
func main() {
num := 10
a := func() {
num++ // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包
fmt.Println(num) // 11
}
a()
}
- 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
- 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
package main
import "fmt"
func main() {
res := addUpper() // 执行addUpper函数,得到一个闭包
fmt.Println(res()) // 2
fmt.Println(res()) // 3
fmt.Println(res()) // 4
fmt.Println(res()) // 5
}
func addUpper() func() int {
x := 1
return func() int {
x++ // 匿名函数中用到了addUpper中的x,所以这是一个闭包
return x
}
}