• go 函数进阶


    回调函数和闭包

    当函数具备以下两种特性的时候,就可以称之为高阶函数(high order functions):

    1. 函数可以作为另一个函数的参数(典型用法是回调函数)
    2. 函数可以返回另一个函数,即让另一个函数作为这个函数的返回值(典型用法是闭包)

    一般来说,附带的还具备一个特性:函数可以作为一个值赋值给变量。

    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	out:%d
    ", in, out)
    }
    

    以上就是一个典型的例子, 可以利用闭包共享变量的特性,在使用全局实例变量后,实现累加操作。

    参考

    ♥永远年轻,永远热泪盈眶♥
  • 相关阅读:
    linux常用统计命令
    linux文件处理命令
    linux三剑客和管道使用
    bash编程语法
    第八章:用通配符进行过滤
    第七章:数据过滤
    第六章:过滤数据
    第五章:排序检索数据
    第四章:检索数据
    第二章:MYSQL简介
  • 原文地址:https://www.cnblogs.com/failymao/p/14716868.html
Copyright © 2020-2023  润新知