Go语言for-range函数使用技巧实例探究

 更新时间:2024年01月21日 10:16:36   作者:晁岳攀(鸟窝) 鸟窝聊技术  
这篇文章主要为大家介绍了Go语言for-range函数使用技巧实例探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Go range函数

Go 1.22 中可以 range 一个整数,比如下面的代码:

for i := range 10 {
    fmt.Println(i)
}

这个大家都已经知道了,其实对应的提案中还有一个隐藏的功能,就是可以 range 一个函数,比如下面的代码(摘自官方代码库internal/trace/v2/event.go[1]):

// Frames is an iterator over the frames in a Stack.
func (s Stack) Frames(yield func(f StackFrame) bool) bool {
 if s.id == 0 {
  return true
 }
 stk := s.table.stacks.mustGet(s.id)
 for _, f := range stk.frames {
  sf := StackFrame{
   PC:   f.pc,
   Func: s.table.strings.mustGet(f.funcID),
   File: s.table.strings.mustGet(f.fileID),
   Line: f.line,
  }
  if !yield(sf) {
   return false
  }
 }
 return true
}

就少有介绍了。

本文尝试介绍它,让读者先了解一下,它在 Go 1.22 中是一个实验性的功能,还不确定未来在哪个版本中会被正式支持。

官方 wiki 中也有一篇介绍: Rangefunc Experiment[2],类似问答的形式,也是必读的知识库。

这个功能去年 Russ Cox 发起讨论(#56413[3]), 并建立一个提案(#61405[4]),大家讨论都很激烈啊,几百次的讨论,所以我也不准备介绍前因后果了,直接了当的说结论。 先前, for-range所能遍历(迭代)的类型很有限,只能是 slice、数组、map、字符串、channel 等。 现在,除了上面的五种类型,还可以是整数和三种三种函数。

当然for x := range n { ... }等价于for x := T(0); x < n; x++ { ... }, 其中 T 是 n 的类型。这个大家都知道了。

range 的类型

三个函数可能大家不是很了解,很正常,目前这只是一个实验性的功能。当然 range 的类型如下:

Range 表达式第一个值第二个值
array or slice a [n]E, *[n]E, or []Eindex i inta[i] E
string s string typeindex i intsee below rune
map m map[K]Vkey k Km[k] V
channel c chan E, <-chan Eelement e E
integer n integer typeindex i int
function, 0 values f func(func()bool) bool
function, 1 value f func(func(V)bool) boolvalue v V
function, 2 values f func(func(K, V)bool) boolkey k Kv V

本文介绍的就是后三种形式

三种可遍历的函数

假设f是一个这样的函数:func(func()bool) bool, 那么for x := range f { ... }类似于f(func(x T1, y T2) bool { ... }),其中 for 循环移动到方法体中了。yield的 bool 返回值指示是否还要继续遍历。

对于这样一个f,下面的格式都可以:

for x, y := range f { ... }
for x, _ := range f { ... }
for _, y := range f { ... }
for x := range f { ... }
for range f { ... }

下面是一个例子:

 var fn = func(yield func(k int, v byte) bool) {
  for i := 0; i < 26; i++ {
   if !yield(i, byte('a'+i)) {
    return
   }
  }
 }

 for k, v := range fn {
  fmt.Printf("%d: %c\n", k, v)
 }

运行可以看到结果符合预期,我们遍历了 26 个小写字母,注意 range 的数据类型是我们的函数:

这里,fn 这个函数没有返回值,其实也可以有 bool 返回值,有 bool 返回值就可以组合多个 range 函数,可以容易写出复杂且难以维护的代码,减少自己失业的可能。 这里的yield函数接收两个参数,第一个是int类型,第二个是byte类型,返回值是bool类型,这个yield函数的返回值决定了是否继续遍历。当然这里我们可以写泛型的程序,这里为了简单,就不写了。

下面是一个ffunc(func(V)bool) bool的例子:

    var fn = func(yield func(v byte) bool) {
        for i := 0; i < 26; i++ {
            if !yield(byte('a' + i)) {
                return
            }
        }
    }

    for v := range fn {
        fmt.Printf("%c\n", v)
    }

当然 yield 函数也可以没有参数,比如func(func()bool) bool,下面这个例子就是无参数的形式,输出结果是 26。

package main

import "fmt"

func main() {
 var fn = func(yield func() bool) {
  for i := 0; i < 26; i++ {
   if !yield() {
    return
   }
  }
 }

 var count int
 for range fn {
  count++
 }
 fmt.Println(count)
}

如果不使用 for-range 函数的形式,我们可以进行改写,比如两个参数的列子:

 var fn = func(yield func(k int, v byte) bool) {
  for i := 0; i < 26; i++ {
   if !yield(i, byte('a'+i)) {
    return
   }
  }
 }

 fn(func(k int, v byte) bool {
  fmt.Printf("%d: %c\n", k, v)
  return true
 })

注意yield参数名称不是一个关键字,它只是一个普通的参数名称,可以随便取名字,但是为了模仿和其它语言中的generator,使用了yield这样一个名称,以至于代码更加易读。

看起来这个功能就是一个语法糖, 代码rangefunc/rewrite[5]将 range-over-func 代码写成非 range-over-func 代码的形式。

为什么要这样做?

标准库中就有archive/tar.Reader.Nextbufio.Reader.ReadBytebufio.Scanner.Scancontainer/ring.Ring.Dodatabase/sql.Rowsexpvar.Doflag.Visitgo/token.FileSet.Iteratepath/filepath.Walkgo/token.FileSet.Iterateruntime.Frames.Next 和sync.Map.Range等各种遍历的函数,所以如果有一种统一的格式更好。

第三方库中有更多的类似代码。

虽然这个功能还没有正式支持,但是我看到有些库摩拳擦掌准备使用了,而sqlrange[6]更进一步,已经支持了。

当然你使用它必须下载 Go 1.22 或者 gotip, 并且设置export GOEXPERIMENT=rangefunc

它提供了QueryExec可遍历函数。比如Query从一个表中查询Point数据:

type Point struct {
    X float64 `sql:"x"`
    Y float64 `sql:"y"`
}

for p, err := range sqlrange.Query[Point](db, `select x, y from points` "Point") {
    if err != nil {
        ...
    }
    ...
}

遍历查询和 ORM 一气呵成。这里的资源管理是自动的,底层的*sql.Rows遍历完会自动关闭。

参考文档

[1]internal/trace/v2/event.go: https://github.com/golang/go/blob/97daa6e94296980b4aa2dac93a938a5edd95ce93/src/internal/trace/v2/event.go#L262 

[2]Rangefunc Experiment: https://go.dev/wiki/RangefuncExperiment 

[3]#56413: https://github.com/golang/go/discussions/56413 

[4]#61405: https://github.com/golang/go/issues/61405 

[5]rangefunc/rewrite: https://go.googlesource.com/go/+/refs/changes/41/510541/7/src/cmd/compile/internal/rangefunc/rewrite.go 

[6]sqlrange: https://github.com/achille-roussel/sqlrange 

以上就是Go语言for-range函数使用技巧实例探究的详细内容,更多关于go for-range函数的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言CSP并发模型实现MPG

    Go语言CSP并发模型实现MPG

    这篇文章主要为大家介绍了Go语言CSP并发模型实现MPG图文详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • golang文件服务器的两种方式(可以访问任何目录)

    golang文件服务器的两种方式(可以访问任何目录)

    这篇文章主要介绍了golang文件服务器的两种方式,可以访问任何目录,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • Go中strings的常用方法详解

    Go中strings的常用方法详解

    这篇文章主要介绍了Go中strings的常用方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • Go语言实现本地缓存的策略详解

    Go语言实现本地缓存的策略详解

    今天给大家分享的是Go语言本地缓存的一些内容,主要是结合bigcache和fastcache两个优秀的开源代码库,总结一些设计思路和感悟,文章通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • Go使用协程批量获取数据加快接口返回速度

    Go使用协程批量获取数据加快接口返回速度

    这篇文章主要介绍了Go使用协程批量获取数据加快接口返回速度,使用Go语言后,可以并发获取,极大提升效率,需要的朋友可以参考下
    2023-02-02
  • Go语言并发编程之互斥锁Mutex和读写锁RWMutex

    Go语言并发编程之互斥锁Mutex和读写锁RWMutex

    Go 语言中提供了很多同步工具,本文将介绍互斥锁Mutex和读写锁RWMutex的使用方法,想要具体了解的小伙伴,请参考下面文章详细内容,希望对你有所帮助
    2021-10-10
  • golang如何去除字符串的换行符

    golang如何去除字符串的换行符

    这篇文章主要介绍了golang如何去除字符串的换行符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Golang中闭包与常用场景详解

    Golang中闭包与常用场景详解

    在很多的开源项目里,经常看到闭包的运用,这篇文章主要为大家简要记录一下闭包的概念和一些常用的场景,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-11-11
  • 浅析Golang中类型嵌入的简介与使用

    浅析Golang中类型嵌入的简介与使用

    类型嵌入指的就是在一个类型的定义中嵌入了其他类型,Go 语言支持两种类型嵌入,分别是接口类型的类型嵌入和结构体类型的类型嵌入,下面我们就来详细一下类型嵌入的使用吧
    2023-11-11
  • 使用Lumberjack+zap进行日志切割归档操作

    使用Lumberjack+zap进行日志切割归档操作

    这篇文章主要介绍了使用Lumberjack+zap进行日志切割归档操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12

最新评论