go 函数进阶
回调函数和闭包
当函数具备以下两种特性的时候,就可以称之为高阶函数(high order functions):
- 函数可以作为另一个函数的参数(典型用法是回调函数)
- 函数可以返回另一个函数,即让另一个函数作为这个函数的返回值(典型用法是闭包)
一般来说,附带的还具备一个特性:函数可以作为一个值赋值给变量。
f := func(){...}
f()
由于Go中函数不能嵌套命名函数,所以函数返回函数的时候,只能返回匿名函数。
先简单介绍下高阶函数,然后介绍闭包。
高阶函数示例
例如,将函数作为另一个函数的参数:
package main
import "fmt"
func added(msg string, a func(a, b int) int) {
fmt.Println(msg, ":", a(33, 44))
}
func main() {
// 函数内部不能嵌套命名函数
// 所以main()中只能定义匿名函数
f := func(a, b int) int {
return a + b
}
added("a+b", f)
}
以下示例是函数返回另一个函数:
package main
import "fmt"
func added() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}
func main() {
m := added()
fmt.Println(m(33, 44))
}
回调函数(sort.SliceStable)
将函数B作为另一个函数A的参数,可以使得函数A的通用性更强,可以随意定义函数B,只要满足规则,函数A都可以去处理,这比较适合于回调函数。
在Go的sort包中有一个很强大的Slice排序工具SliceStable(),它就是将排序函数作为参数的
func SliceStable(slice interface{}, less func(i, j int) bool)
这个函数是什么意思呢?给定一个名为slice的Slice结构,使用名为less的函数去对这个slice排序。这个less函数的结构为less func(i, j int) bool,其中i和j指定排序依据。
Go中已经内置好了排序的算法,我们无需自己去定义排序算法,Go会自动从Slice中每次取两个i和j索引对应的元素,然后去回调排序函数less。所以我们只需要传递升序还是降序、根据什么排序就可以。
package main
import (
"fmt"
"sort"
)
func main() {
s1 := []int{112, 22, 52, 32, 12}
// 定义排序函数
less := func(i, j int) bool {
// 降序排序
return s1[i] > s1[j]
// 升序排序:s1[i] < s1[j]
}
//
sort.SliceStable(s1, less)
fmt.Println(s1)
}
这里的排序函数就是回调函数。每取一次i和j对应的元素,就调用一次less函数。
闭包
简单地说,闭包就是"一个函数+一个作用域环境"组成的特殊函数。这个函数可以访问不是它自己内部的变量,也就是这个变量在其它作用域内,且这个变量是未赋值的,而是等待我们去赋值的。
例如:
package main
import "fmt"
func f(x int) func(int) int{
g := func(y int) int{
return x+y
}
// 返回闭包
return g
}
func main() {
// 将函数的返回结果"闭包"赋值给变量a
a := f(3)
// 调用存储在变量中的闭包函数
res := a(5)
fmt.Println(res)
// 可以直接调用闭包
// 因为闭包没有赋值给变量,所以它称为匿名闭包
fmt.Println(f(3)(5))
}
上面的f()返回的g之所以称为闭包函数,是因为它是一个函数,且引用了不在它自己范围内的变量x,这个变量x是g所在作用域环境内的变量,因为x是未知、未赋值的自由变量。
如果x在传递给g之前是已经赋值的,那么闭包函数就不应该称为闭包,因为这样的闭包已经失去意义了。
下面这个g也是闭包函数,但这个闭包函数是自定义的,而不是通过函数返回函数得到的。
package main
import "fmt"
func main() {
// 自由变量x
var x int
// 闭包函数g
g := func(i int) int {
return x + i
}
x = 5
// 调用闭包函数
fmt.Println(g(5))
x = 10
// 调用闭包函数
之所以这里的g也是闭包函数,是因为g中访问了不属于自己的变量x,而这个变量在闭包函数定义时是未绑定值的,也就是自由变量。
闭包的作用很明显,在第一个闭包例子中,f(3)退出后,它返回的闭包函数g()仍然记住了原本属于f()中的x=3。这样就可以让很多闭包函数共享同一个自由变量x的值。
例如,下面的a(3)、a(5)、a(8)都共享来自f()的x=3。
a := f(3)
a(3)
a(5)
a(8)
再往外层函数看,f(3)可以将自由变量x绑定为x=3,自然也可以绑定为x=5、x=8等等。
所以,什么时候使用闭包?一般来说,可以将过程分成两部分或更多部分都进行工厂化的时候,就适合闭包(实际上,有些地方直接将闭包称呼为工厂函数)。
第一个部分是可以给自由变量批量绑定不同的值,
第二部分是多个闭包函数可以共享第一步绑定后的自由变量。
最佳闭包实例
场景: 使用结构体统计所有应用的进出口流量,同时为了避免多个应用同时对结构体进行数据修改,必须保证原子操作。 代码如下:
package main
import (
"fmt"
"sync/atomic"
)
// 使用全局变量实例化结构体
var bps = NewBps()
type Bps struct {
InByteAddF func(uint64) // 辅助字段,用来对入口流量进行操作
InByteCountF func() uint64 // 函数计算当前入口流量
OutByteAddF func(uint64)
OutByteCountF func() uint64
}
// 函数,返回两个函数,函数引用了外部变量,属于闭包
// 使用atomic包保证原子操作
func addFunc() (func(n uint64), func() uint64) {
var count uint64 = 0
return func(n uint64) {
atomic.AddUint64(&count, n)
}, func() uint64 {
c := count
return c
}
}
// 工厂函数
func NewBps() *Bps {
b := &Bps{}
b.InByteAddF, b.InByteCountF = addFunc()
b.OutByteAddF, b.OutByteCountF = addFunc()
return b
}
// 计算各个应用流量加入到总流量
func (b *Bps) Add(bytes uint64, in bool) {
if in {
b.InByteAddF(bytes)
} else {
b.OutByteAddF(bytes)
}
}
// Traffic, update the network in and out traffic data
// @param: access, flag if is entrance traffic
// @param: bytes, the data of current traffic
func Traffic(access bool, bytes uint64) {
in := !access
bps.Add(bytes, in)
}
// SampleBitrate,calculate all of network in and out traffic.
// unit is Bit
func SampleBitrate() (int64, int64) {
return int64(bps.InByteCountF()), int64(bps.OutByteCountF())
}
func main() {
type sub struct {
access bool
data uint64
}
testData := make([]sub, 6)
// 代表不同的应用,收集进出口流量
testData = append(testData, sub{true, 10})
testData = append(testData, sub{false, 10})
testData = append(testData, sub{false, 100})
testData = append(testData, sub{false, 1000})
testData = append(testData, sub{true, 1000})
testData = append(testData, sub{true, 100})
for _, s := range testData {
Traffic(s.access, s.data)
}
in, out := SampleBitrate()
fmt.Printf("in:%d\tout:%d\n", in, out)
}
以上就是一个典型的例子, 可以利用闭包共享变量的特性,在使用全局实例变量后,实现累加操作。
go 函数进阶的更多相关文章
- 深入理解javascript函数进阶系列第一篇——高阶函数
前面的话 前面的函数系列中介绍了函数的基础用法.从本文开始,将介绍javascript函数进阶系列,本文将详细介绍高阶函数 定义 高阶函数(higher-order function)指操作函数的函数 ...
- 【python 3】 函数 进阶
函数进阶 1.函数命名空间和作用域 命名空间一共分为三种: 全局命名空间 局部命名空间 内置命名空间 *内置命名空间中存放了python解释器为我们提供的名字:input , print , str ...
- day11.1函数进阶 列表集合 字典中的函数变量,函数作为形参
函数进阶 1.函数作为变量 a=123 name="gao" nums=[1,2,3] data=nums#指向同一个内存地址 #查看内存地址篇章 def func(): prin ...
- python基础 (初识函数&函数进阶)
函数基础部分 .什么是函数? 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率. 2.定义函数 定义:def 关键词开头,空格之后接函数名 ...
- day 10 - 1 函数进阶
函数进阶 命名空间和作用域 命名空间 命名空间 有三种内置命名空间 —— python解释器 就是python解释器一启动就可以使用的名字存储在内置命名空间中 内置的名字在启动解释器的时候被加载进内存 ...
- python基础之 初识函数&函数进阶
函数基础部分 1.什么是函数? 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段.函数能提高应用的模块性,和代码的重复利用率. 2.定义函数 定义:def 关键词开头,空格之后接函数名 ...
- python函数进阶(函数参数、返回值、递归函数)
函数进阶 目标 函数参数和返回值的作用 函数的返回值 进阶 函数的参数 进阶 递归函数 01. 函数参数和返回值的作用 函数根据 有没有参数 以及 有没有返回值,可以 相互组合,一共有 4 种 组合形 ...
- python大法好——递归、内置函数、函数进阶
1.递归(自己干自己) def fun(n): n=n/2 print(n) if(n>2) fun(n) #函数调用 fun(10) 结果是5 2 1 0 递归的执行过程:递归执行到最里面一层 ...
- 洗礼灵魂,修炼python(25)--自定义函数(6)—从匿名函数进阶话题讲解中解析“函数式编程”
匿名函数进阶 前一章已经说了匿名函数,匿名函数还可以和其他内置函数结合使用 1.map map():映射器,映射 list(map(lambda x:x*2,range(10))) #把range产生 ...
- 【转】Python之函数进阶
[转]Python之函数进阶 本节内容 上一篇中介绍了Python中函数的定义.函数的调用.函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性: 递归函数 嵌套函数与闭包 匿名函数 高阶函 ...
随机推荐
- Treevalue(0x01)——功能概述
TreeValue--一个通用树状数据结构与函数计算库 Treevalue v1.0.0版本已经于2021年10月24日正式发布,欢迎下载体验:opendilab / treevalue. 这算是tr ...
- PCIE笔记--PCIe错误定义与分类
转载地址:http://blog.chinaaet.com/justlxy/p/5100057782 前面的文章提到过,PCI总线中定义两个边带信号(PERR#和SERR#)来处理总线错误.其中PER ...
- 《手把手教你》系列技巧篇(三十八)-java+ selenium自动化测试-日历时间控件-下篇(详解教程)
1.简介 理想很丰满现实很骨感,在应用selenium实现web自动化时,经常会遇到处理日期控件点击问题,手工很简单,可以一个个点击日期控件选择需要的日期,但自动化执行过程中,完全复制手工这样的操作就 ...
- 【java+selenium3】Actions模拟鼠标 (十一)
一.鼠标操作 WebElement的click()方法可实现元素的点击操作,但是没有提供鼠标的右击/双击/悬停/鼠标拖动等操作.这些操作需要通过Action类提供的方法来实现! Action常用的ap ...
- js实现一个小游戏(飞翔的jj)
js实现一个小游戏(飞翔的jj) 源代码+素材图片在我的仓库 <!DOCTYPE html> <html lang="en"> <head> & ...
- 2021 羊城杯WriteUP
比赛感受 题目质量挺不错的,不知道题目会不会上buu有机会复现一下,躺了个三等奖,发下队伍的wp Team BinX from GZHU web Checkin_Go 源码下载下来发现是go语言写的 ...
- python datetime 增减以月为单位的时间段
datetime.timedelta(days=10) # 可以 datetime.timedelta(months=1) # 不可以 替代: from datetime import date ...
- spring定时任务ThreadPoolTaskScheduler使用注意事项之线程池大小
背景 最近小伙伴解决了一个工单,描述为"手工推送案件无法推,提示token失效",当前工单状态为待关闭,解决方案为"东软接口不稳定造成的,东软的接口恢复正常后,问题解决& ...
- 高德地图API中折线polyline不能跨越180度经度线的解决方案
1.问题 最近在使用高德地图的API,有一个需求是画出对象的历史轨迹,采用了高德地图API中的折线polyline函数.但如果需要跨180度经度线的折线,会出现不能跨越的情况,如下图所示: 图中有三个 ...
- python3排序 sorted(key=lambda)--实现对字典按value值排序
使用python对列表(list)进行排序,说简单也简单,说复杂也复杂,我一开始学的时候也搞不懂在说什么,只能搜索一些英文文章看看讲解,现在积累了一些经验,写在这里跟大家分享, 1.sorted函数首 ...