在 Go 语言中,函数是一等的(first-class)公民,函数类型也是一等的数据类型,本文主要对golang函数的高级用法(回调、函数类型、匿名函数、闭包函数、高阶函数)进行介绍。
在 Go 语言中,函数是一等的(first-class)公民,函数类型也是一等的数据类型,有必要掌握go函数的各种用法,基本用法就不在此赘述了,下面主要介绍一些高级用法。
函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。下面是一个简单的例子:
package main
import (
"fmt"
)
func main() {
callback(1, Add) //输出 The sum of 1 and 2 is: 3
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
在 Go 语言中,函数类型也是一等的数据类型。简单来说,这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等,就像切片和字典的值那样。
而更深层次的含义就是:函数值可以由此成为能够被随意传播的独立逻辑组件(或者说功能模块)。
对于函数类型来说,它是一种对一组输入、输出进行模板化的重要工具,它比接口类型更加轻巧、灵活,它的值也借此变成了可被热替换的逻辑组件。比如下面的代码:
package main
import "fmt"
type Printer func(contents string) (n int, err error)
//注意这里的写法,在类型声明的名称右边的是func关键字,我们由此就可知道这是一个函数类型的声明。
func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}
func main() {
var p Printer
p = printToStd
p("something")
}
这里,首先声明了一个函数类型,名叫Printer;然后在下面声明的函数printToStd的签名与Printer的是一致的,因此前者是后者的一个实现,即使它们的名称以及有的结果名称是不同的。然后在main函数中的代码,将printToStd函数赋给了Printer类型的变量p,并且成功地调用了它。
注意:函数参数、返回值以及它们的类型被统称为函数签名。只要两个函数的参数列表和结果列表中的元素顺序及其类型是一致的,我们就可以说它们是一样的函数,或者说是实现了同一个函数类型的函数。
匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成
// 不带函数名 匿名函数直接赋值给一个变量:
who := func (name string, age int) (string, int) {
return name, age
}
a,b := who("age",20)
fmt.Println(a,b) //Runsen 20
简单地说,当匿名函数引用了外部作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的核心。
闭包引用了函数体之外的变量,这个变量有个专门的术语称呼它,叫自由变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。没有闭包的时候,函数就是一次性买卖,函数执行完毕后就无法再更改函数中变量的值(应该是内存释放了);有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态,因此可以后期更改函数中变量的值(因为这样就不会被go给回收内存了,会一直缓存在那里)。
使用闭包的意义是什么?主要就是缩小变量作用域,减少对全局变量的污染。
比如实现这样一个计算功能:一个数从0开始,每次加上自己的值和当前循环次数(当前第几次,循环从0开始,到9,共10次),然后*2,这样迭代10次。
没有闭包的时候这么写:
func adder(x int) int {
return x * 2
}
func main() {
var a int
for i := 0; i < 10; i ++ {
a = adder(a+i)
fmt.Println(a)
}
}
如果用闭包的话就可以这样写:
func adder() func(int) int {
res := 0
return func(x int) int {
res = (res + x) * 2
return res
}
}
func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Println(a(i))
}
}
从上面的例子可以看出,有3个好处:
1、不是一次性消费,被引用声明后可以重复调用,同时变量又只限定在函数里,同时每次调用不是从初始值开始(函数里长期存储变量)
其实有点像使用面向对象的感觉,实例化一个类,这样这个类里的所有方法、属性都是为某个人私有独享的。但比面向对象更加的轻量化
2、用了闭包后,主函数就变得简单了,把算法封装在一个函数里,使得主函数省略了a=adder(a+i)这种麻烦事了
3、变量污染少,因为如果没用闭包,就会为了传递值到函数里,而在函数外部声明变量,但这样声明的变量又会被下面的其他函数或代码误改。
什么是高阶函数?简单地说,高阶函数可以满足下面的两个条件:
1. 接受其他的函数作为参数传入;
2. 把其他的函数作为结果返回。
只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。高阶函数也是函数式编程中的重要概念和特征。
举一个例子,我想通过编写calculate函数来实现两个整数间的加减乘除运算,但是希望两个整数和具体的操作都由该函数的调用方给出,那么,这样一个函数应该怎样编写呢。
首先,我们来声明一个名叫operate的函数类型,它有两个参数和一个结果,都是int类型的,operate作为参数传入。
type operate func(x, y int) int
再声明一个名叫genCalculator的函数类型,它作为函数的返回结果。
type calculateFunc func(x int, y int) (int, error)
这样,我们传入不同的operate,就会执行不同的运算,得到相应在结果。完整代码如下:
package main
import (
"errors"
"fmt"
)
type operate func(x, y int) int
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() {
x, y := 3, 4
op := func(x, y int) int {
return x + y
}
add := genCalculator(op) // 加法
result, err := add(x, y)
fmt.Printf("The addition result: %d (error: %v)\n",
result, err)
op1 := func(x, y int) int {
return x * y
}
multi := genCalculator(op1) //乘法
result, err = multi(x, y)
fmt.Printf("The multiplication result: %d (error: %v)\n",
result, err)
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。