在Go语言中,函数是一等的公民,函数类型也是一等的数据类型。
函数不但可以用于封装代码、分割功能、解耦逻辑,还可以作为参数值在函数间传递、赋给变量、做类型判断和转换等,
函数值可以由此成为能够被随意传播的独立逻辑组件
函数类型是一种对一组输入、输出进行模板化的工具,使得函数值变成了可被热替换的逻辑组件,如例
package main
import "fmt"
type Printer func(content string) (n int, err error)
func printToStd(content string) (bytesNum int, err error){
return fmt.Println(content)
}
func main(){
var p Printer
p = printToStd
p("something")
}
type XXX func()()声明一个函数类型,这里函数名称和func互换了一下位置而已。
函数签名是函数的参数列表和结果列表的统称,它定义的可用来鉴别不同函数的那些特征,只要两个函数的参数列表和结果列表的元素顺序及其类型是一致的,那么他们就是一样的函数或者说是实现了同一个函数类型的函数
在上例中函数printToStd的签名和Printer的是一致的,因此printToSad其实是Printer的一个实现,把printToSad函数赋给了Printer类型的变量p,并且成功的调用了它。
函数类型是引用类型,它的值可以为nil,零值就是nil。
什么是高阶函数?
1,接受其他的函数作为参数返回
2,把其他的函数作为结果返回
只要满足这两个中的一个,这个函数就是一个高阶函数。
如何编写高阶函数?
(1)接受其他函数作为参数返回的用法
比如写出一个函数代码,通过calculate函数实现两个整数间的加减乘除运算,但是希望两个整数和具体的操作都由该函数的调用方给出。
首先,声明一个函数类型:
type operate func(x, y int) int
然后,编写calculate函数的签名部分(函数名,输入、输出参数),输入参数中接受函数类型。
func calculate(x int, y int, op operate) (int, error){
if op == nil {
return 0, errors.New("Invalid operation")
}
return op(x, y), nil
}
我们把函数作为参数在其他函数间传递,如op就是一个operate类型的参数。
也可以编写匿名函数,如:
op := func(x,y int) int {
return x+y
}
(2)把其他的函数作为结果返回
package main
import (
"errors"
"fmt"
)
type operate func(x, y int) int
// 方案1。
func calculate(x int, y int, op operate) (int, error) {
if op == nil {
return 0, errors.New("invalid operation")
}
return op(x, y), nil
}
// 方案2。
type calculateFunc func(x int, y int) (int, error)
func genCalculator(op operate) calculateFunc {
return func(x int, y int) (int, error) {
if op == nil {
return 0, errors.New("invalid operation")
}
return op(x, y), nil
}
}
func main() {
// 方案1。
x, y := 12, 23
op := func(x, y int) int {
return x + y
}
result, err := calculate(x, y, op)
fmt.Printf("The result: %d (error: %v)\n",
result, err)
result, err = calculate(x, y, nil)
fmt.Printf("The result: %d (error: %v)\n",
result, err)
// 方案2。
x, y = 56, 78
add := genCalculator(op)
result, err = add(x, y)
fmt.Printf("The result: %d (error: %v)\n",
result, err)
}
闭包是在一个函数中存在对外来标识符的引用。外来标识符即不代表当前函数的任何参数或结果的变量(又称自由变量),是直接从外边拿过来的。表达式:func () (func())
func genCalculator(op operate) calculateFunc {
return func(x int, y int) (int, error) {
if op == nil {
return 0, errors.New("Invalid operation")
}
return op(x, y), nil
}
}
上述示例中genCalculator函数内部,就实现了一个闭包,而其genCalculator函数本身就是一个高阶函数。其内部的外来标识符有x,y,op,其中op是一个自由变量,只有在genCalculator函数被调用的时候,op参数才能知道代表什么。当执行到if op == nil这行时,go语言编译器读到这里会试图去寻找op所代表的东西,它会发现op代表的是genCalculator函数的参数,如此一来这个闭包函数的状态就由“不确定”变成了“确定”,或者说转到了“闭合”状态,由此也就真正形成了一个闭包。
实现闭包的意义
表面上看,我们只是延迟实现了一部分的程序逻辑或功能而已,但实际上,我们是在动态的生成那部分的程序逻辑。我们可以使用闭包在程序运行的过程中,根据需要生成功能不同的函数,并影响后续的程序行为。类似GoF设计模式中的模板方法。
package main
import (
"fmt"
)
func modifyArray(a [3]string) [3]string {
a[1] = "x"
return a
}
func main() {
a1 := [3]string{"a", "b", "c"}
fmt.Printf("The array a1 is: %v\n", a1)
a2 := modifyArray(a1)
fmt.Printf("The modified array of a2 is: %v\n", a2)
fmt.Printf("The original a1 is: %v\n", a1)
}
该示例中,所有传给函数的参数值都会被复制,函数在其内部使用的并不是参数值的原值,而是它的副本。
在本例中,修改的知识原数组的副本而已,不会对原数组造成影响。
打印结果为
The array: [a b c]
The modified array: [a x c]
The original array: [a b c]
对于引用类型,如切片、字典、通道(chan),都是浅表复制,只会拷贝它们本身,而不会拷贝底层数组。
对于值类型的参数值,有些情况会被改变。如下例
package main
import (
"fmt"
)
func modifyComplexArray(a [3][]string) [3][]string {
a[1][1] = "s"
a[2] = []string{"o", "p", "q"}
return a
}
func main() {
complexArray1 := [3][]string{
[]string{"d", "e", "f"},
[]string{"g", "h", "i"},
[]string{"j", "k", "l"},
}
fmt.Printf("The complex array: %v\n", complexArray1)
complexArray2 := modifyComplexArray(complexArray1)
fmt.Printf("The modified complex array: %v\n", complexArray2)
fmt.Printf("The original complex array: %v\n", complexArray1)
}
打印结果为
The complex array: [[d e f] [g h i] [j k l]]
The modified complex array: [[d e f] [g s i] [o p q]]
The original complex array: [[d e f] [g s i] [j k l]]
1,函数真正拿到的参数值只是它们的副本,那么函数返回给调用方的结果值也会被复制吗?
答:是复制的,不是原值。可以从引用地址看出来