在Go语言中,函数是一等公民,函数类型也是一等的数据类型。简单来说,这意味着:函数不但可以用于封装代码,分割功能,解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等。
package mainimport "fmt"type Printer func(contents string) (n int, err error)func printToStd(contents string) (bytesNum int, err error) { return fmt.Println(contents)}func main() { var p Printer p = printToStd p("something")}
函数的什么使用type
关键字。type后面是函数类型名称,紧接着是func关键字。func右边的就是这个函数类型的参数列表和结果列表。
函数的签名就是函数的参数列表和结果列表的统称。各个参数和结果的名称不能算做函数签名的一部分,甚至结果列表可以没有名称。严格来说,函数的名称也不能算作函数签名的一部分,它只是我们在调用函数时需要给定的标识符而已。
只要两个函数的参数列表和结果列表中的元素顺序及其类型是一致的,我们就可以说它们是一样的函数,或者说它们是实现了同一个函数类型的函数。
上面的代码中printToStd的签名和Printer的是一致的,因此前者是后者的一个实现。
高阶函数
Go语言在语言层面支持了函数式编程。
什么是高阶函数?
满足下面的两个条件的任意一个的函数就是高阶函数:
- 接受其他的函数作为参数传入。
- 把其他的函数作为结果返回。
type operate func(x, y int) intfunc add(x,y int)int { return x + y}func calculate(x int, y int, op operate) (int, error) { if op == nil { return 0, errors.New("invalid operation") } return op(x, y), nil}
上面的calculate函数就是一个高阶函数。
实现闭包
自由变量:
在一个函数中,有一个外来标识符,它既不是函数的参数,也不是函数的结果,更不是函数内部声明的。它是直接从外边拿过来的。专业属于称呼为“自由变量”
闭包函数就是因为引用了自由变量,而呈现出一种“不确定”的状态,也叫“开放”状态。也就是说闭包的内部逻辑并不是完整的,有一部分逻辑需要这个自由变量参与完成,而自由变量到底代表了什么在闭包函数被定义的时候是未知的。在Go语言定义闭包函数的时候最多知道自由变量的类型。
type operate func(x, y int) inttype calculateFunc func(x, y int) (int, error)func genCalculator(op operate) calculateFunc { return func(x, y int) (i int, e error) { if op == nil { return 0, errors.New("invalid opration") } return op(x, y), nil }}func main(){ op := func(x, y int) int { return x + y } add := genCalculator(op) result, err := add(3, 4) fmt.Printf("the result: %d (error: %v)\n",result,err)}
genCalculator函数内部就实现了闭包:那就是定义一个匿名的calculateFunc类型的函数并把它作为结果值返回。这个匿名的函数就是一个闭包函数。因为这个匿名函数里使用的变量op既不是它的任何参数或结果,也不是它自己声明的,而是定义它的genCalculator函数的参数,所以op在匿名函数中是一个自由变量。
这个自由变量究竟代表了什么,这一点并不是在定义这个闭包函数的时候确定的,而是在genCalculator函数被调用的时候确定的。只有给定了该参数op,我们才直到它返回给我们的闭包函数可以用于什么计算。
if op == nil
Go语言编译器读到这里的时候会试图去寻找op锁代表的东西,它会发现op代表的是genCalculator函数的参数,然后,编译器就会把两者联系起来,这时可以说:自由变量op被“捕获”了。当程序运行到这里,op就是genCalculator的参数值了,这时,闭包函数的状态就由“不确定”变为了“确定”,或者说转到了“闭合”状态,至此,也就真正形成了一个闭包。
闭包的意义
表面上看,我们只是延迟实现了一部分程序逻辑或功能而已。
实际上,我们是在动态地生成那部分逻辑功能。
传入函数的参数
package mainimport "fmt"func main() { array1 := [3]string{"a", "b", "c"} fmt.Printf("The array: %v\n", array1) array2 := modifyArray(array1) fmt.Printf("The modified array: %v\n", array2) fmt.Printf("The original array: %v\n", array1)}func modifyArray(a [3]string) [3]string { a[1] = "x" return a}
原数组不变。
原因是所有传递给函数的参数值都会被复制,函数在其内部使用的并不是参数值的原值,而是它的副本。
注意,对于引用类型,切片,字典,通道,复制的是它们本身的值,不会复制它们引用的底层数据,也就是说,这只是浅表复制,不是深层复制。Go语言没有深层复制。
对于值类型的数组,如果它的元素类型是引用类型,当这种数组被传入函数的话,函数中对该参数值的修改会影响到参数吗?例如[3][]string
如果修改数组中的切片的某个元素,会影响原数组;如果修改数组的某个元素就不会影响原数组。
函数返回给调用方的结果值会被复制吗?
当函数返回指针类型时,不会发生拷贝。当函数返回非指针类型并把结果赋值给其他变量时,会发生拷贝。