上线了做网站怎么样/搜索引擎排名优化方案
概要
地球人都知道:函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。go语言中函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可以嵌套定义(使用匿名函数),即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。闭包是一个函数值,他来自函数体外部的变量引用。
下面分别介绍:Go中的函数、函数作为值与类型、函数作为返回值、匿名函数、闭包
Go中的函数
函数是Go里面的核心设计,它通过关键字func来申明,他的格式如下:
函数是Go里面的核心设计,它通过关键字func来申明,他的格式如下
func funcname(input1 type1, input2 type2) (output1 type1, output2 type2) {//这里是处理逻辑代码//返回多个值return value1, value2
}
函数有以下特征:
- 关键字func用来申明一个函数funcname,匿名函数可以没有funcname。
- 函数可以有一个或者多个参数,每个参数后面带有类型,通过,分隔
- 函数可以返回多个值
- 上面返回值申明了两个变量output1和output2,如果你不想申明也可以,直接就两个类型
- 如果只有一个返回值且不申明返回值变量,那么你可以省略用以包括返回值的括号
- 如果没有返回值,那么就直接省略最后的返回信息
函数作为值、类型
在Go中函数也是一种变量,我们可以通过type来定义他,他的类型就是所有拥有相同的参数,相同的返回值的一种类型
type type_name func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子。
package main
import "fmt"type test_int func(int) bool //申明了一个函数类型func isOdd(integer int) bool {if integer%2 == 0 {return false}return true
}func isEven(integer int) bool {if integer%2 == 0 {return true}return false
}//申明的函数类型在这个地方当做了一个参数
func filter(slice []int, f test_int) []int {var result []intfor _, value := range slice {if f(value) {result = append(result, value)}}return result
}func main(){slice := []int {1, 2, 3, 4, 5, 7}fmt.Println("slice = ", slice)odd := filter(slice, isOdd) //函数当做值来传递了fmt.Println("Odd elements of slice are: ", odd)even := filter(slice, isEven)//函数当做值来传递了fmt.Println("Even elements of slice are: ", even)
}
函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到test_int这个类型是一个函数类型,然后两个filter函数的参数和返回值与test_int类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
函数作为返回值
假如你拥有一份吃过寿司的人的清单, 你是否能够根据人名确定他是否在清单上? 这是个很简单的问题, 你只需遍历清单. 嗯, 如果你go的功底很弱, 不知道怎么遍历清单那怎么办? 没关系, 我会给你提供一个刷选器:
func Screen(patients []string) func(string) bool {// 定义匿名函数并返回return func(name string) bool {for _, soul := range patients {if soul == name {return true}}return false}
}
Screen方法会将刷选的函数返回给调用方, 这样你就可以不用懂怎么去遍历清单了, 你只需调用我返回给你的函数就可以:
// 吃过寿司的人的清单
those_who_bought_sushi := []string{"Anand", "JoJo", "Jin", "Mon", "Peter", "Sachin"}
// 得到刷选器函数
bought_sushi := Screen(those_who_bought_sushi)
// 调用刷选器函数就可以知道某人是否在清单上
fmt.Println(bought_sushi("Anand")) // true
fmt.Println(bought_sushi("Alex")) // false
匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式。
在Go语言中,函数可以像普通变量一样被传递或使用,这与C语言的回调函数比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数名的函数声明和函数体组成:
func(a,b int,z float64)bool{return a*b<int(z)
}
匿名函数可以直接赋值给一个变量或者直接执行:
f := func(x, y int) int {return x + y
}func(ch chan int) {ch <- ACK
}(reply_chan) //括号内跟参数列表,表示函数调用
闭包
基本概念
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码快或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象的没有被释放)为自由变量提供绑定的计算环境(作用域)。
闭包的价值
闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。
Go语言中的闭包
Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。示例如下:
package main
import("fmt"
)func main(){var j int = 5a:=func() (func()){var i int = 10return func(){fmt.Printf("i, j:%d, %d\n",i,j)}}()//将一个无需参数返回值为匿名函数的函数赋值给a()a()j*=2// i*=2这样是错的a()
}执行结果:
i, j: 10, 5
1, j: 10, 10
在上面的例子中,变量a指向的闭包函数引用了局部变量i和j,i的值被隔离,在闭包外不能被修改,改变j的值以后,再次调用a,发现结果是修改过的值。
在变量a指向的闭包函数中,只有内部的内部的匿名函数才能访问变量i,而无法通过其他途径访问到,因此保证了i的安全性。
栗子详解
Go指南中有一篇例子,代码如下:
func adder() func(int) int {sum := 0return func(x int) int {sum += xreturn sum}
}
func demoFunction2() {pos, neg := adder(), adder()for i := 0; i < 10; i++ {fmt.Println(pos(i), neg(-2 * i),)}
}
打印出的结果,什么情况?
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
实际上,在pos, neg := adder(), adder()的时候,pos和neg引用不仅得到了上面的那个闭包,也得到了sum这个在引用中的”闭包级别变量”(这个名字我瞎取的,感觉这么形容好点),,所以实际上每次调用pos(i)和neg(-2 * i),都是改变了sum的值并存在pos引用对应的函数中了,可以在这个函数中加上一句代码,让整个过程更加直观:
return func(x int) int {fmt.Println(">>>>>>",sum)sum += xreturn sum
}
当我们运行程序,再次打印结果,就会是下面的:
>>>>>> 0
>>>>>> 0
0 0
>>>>>> 0
>>>>>> 0
1 -2
>>>>>> 1
>>>>>> -2
3 -6
>>>>>> 3
>>>>>> -6
6 -12
>>>>>> 6
>>>>>> -12
10 -20
>>>>>> 10
>>>>>> -20
15 -30
>>>>>> 15
>>>>>> -30
21 -42
>>>>>> 21
>>>>>> -42
28 -56
>>>>>> 28
>>>>>> -56
36 -72
>>>>>> 36
>>>>>> -72
45 -90
这下是不是更好理解了,以pos(i)为例,每次计算完之后,sum保存了计算的结果:
函数 | sum | x | return |
---|---|---|---|
pos(0) | 0 | 0 | 0 |
pos(1) | 0 | 1 | 1 |
pos(2) | 1 | 2 | 3 |
pos(3) | 3 | 3 | 6 |
pos(4) | 6 | 4 | 10 |
pos(5) | 10 | 5 | 15 |
pos(6) | 15 | 6 | 21 |
pos(7) | 21 | 7 | 28 |
pos(8) | 28 | 8 | 36 |
pos(9) | 36 | 9 | 45 |
也就是说,sum
的生命周期是跟接收adder()
的变量pos
, neg
的声明周期是一致的。
End