Go語言for-range函數(shù)使用技巧實(shí)例探究
Go range函數(shù)
Go 1.22 中可以 range 一個(gè)整數(shù),比如下面的代碼:
for i := range 10 {
fmt.Println(i)
}
這個(gè)大家都已經(jīng)知道了,其實(shí)對應(yīng)的提案中還有一個(gè)隱藏的功能,就是可以 range 一個(gè)函數(shù),比如下面的代碼(摘自官方代碼庫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 中是一個(gè)實(shí)驗(yàn)性的功能,還不確定未來在哪個(gè)版本中會(huì)被正式支持。
官方 wiki 中也有一篇介紹: Rangefunc Experiment[2],類似問答的形式,也是必讀的知識(shí)庫。
這個(gè)功能去年 Russ Cox 發(fā)起討論(#56413[3]), 并建立一個(gè)提案(#61405[4]),大家討論都很激烈啊,幾百次的討論,所以我也不準(zhǔn)備介紹前因后果了,直接了當(dāng)?shù)恼f結(jié)論。 先前, for-range所能遍歷(迭代)的類型很有限,只能是 slice、數(shù)組、map、字符串、channel 等。 現(xiàn)在,除了上面的五種類型,還可以是整數(shù)和三種三種函數(shù)。
當(dāng)然for x := range n { ... }等價(jià)于for x := T(0); x < n; x++ { ... }, 其中 T 是 n 的類型。這個(gè)大家都知道了。
range 的類型
三個(gè)函數(shù)可能大家不是很了解,很正常,目前這只是一個(gè)實(shí)驗(yàn)性的功能。當(dāng)然 range 的類型如下:
| Range 表達(dá)式 | 第一個(gè)值 | 第二個(gè)值 |
|---|---|---|
| array or slice a [n]E, *[n]E, or []E | index i int | a[i] E |
| string s string type | index i int | see below rune |
| map m map[K]V | key k K | m[k] V |
| channel c chan E, <-chan E | element e E | |
| integer n integer type | index i int | |
| function, 0 values f func(func()bool) bool | ||
| function, 1 value f func(func(V)bool) bool | value v V | |
| function, 2 values f func(func(K, V)bool) bool | key k K | v V |
本文介紹的就是后三種形式
三種可遍歷的函數(shù)
假設(shè)f是一個(gè)這樣的函數(shù):func(func()bool) bool, 那么for x := range f { ... }類似于f(func(x T1, y T2) bool { ... }),其中 for 循環(huán)移動(dòng)到方法體中了。yield的 bool 返回值指示是否還要繼續(xù)遍歷。
對于這樣一個(gè)f,下面的格式都可以:
for x, y := range f { ... }
for x, _ := range f { ... }
for _, y := range f { ... }
for x := range f { ... }
for range f { ... }
下面是一個(gè)例子:
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)
}
運(yùn)行可以看到結(jié)果符合預(yù)期,我們遍歷了 26 個(gè)小寫字母,注意 range 的數(shù)據(jù)類型是我們的函數(shù):

這里,fn 這個(gè)函數(shù)沒有返回值,其實(shí)也可以有 bool 返回值,有 bool 返回值就可以組合多個(gè) range 函數(shù),可以容易寫出復(fù)雜且難以維護(hù)的代碼,減少自己失業(yè)的可能。 這里的yield函數(shù)接收兩個(gè)參數(shù),第一個(gè)是int類型,第二個(gè)是byte類型,返回值是bool類型,這個(gè)yield函數(shù)的返回值決定了是否繼續(xù)遍歷。當(dāng)然這里我們可以寫泛型的程序,這里為了簡單,就不寫了。
下面是一個(gè)f是func(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)
}

當(dāng)然 yield 函數(shù)也可以沒有參數(shù),比如func(func()bool) bool,下面這個(gè)例子就是無參數(shù)的形式,輸出結(jié)果是 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 函數(shù)的形式,我們可以進(jìn)行改寫,比如兩個(gè)參數(shù)的列子:
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參數(shù)名稱不是一個(gè)關(guān)鍵字,它只是一個(gè)普通的參數(shù)名稱,可以隨便取名字,但是為了模仿和其它語言中的generator,使用了yield這樣一個(gè)名稱,以至于代碼更加易讀。
看起來這個(gè)功能就是一個(gè)語法糖, 代碼rangefunc/rewrite[5]將 range-over-func 代碼寫成非 range-over-func 代碼的形式。
為什么要這樣做?
標(biāo)準(zhǔn)庫中就有archive/tar.Reader.Next, bufio.Reader.ReadByte, bufio.Scanner.Scan, container/ring.Ring.Do, database/sql.Rows, expvar.Do, flag.Visit, go/token.FileSet.Iterate, path/filepath.Walk, go/token.FileSet.Iterate, runtime.Frames.Next 和sync.Map.Range等各種遍歷的函數(shù),所以如果有一種統(tǒng)一的格式更好。
第三方庫中有更多的類似代碼。
雖然這個(gè)功能還沒有正式支持,但是我看到有些庫摩拳擦掌準(zhǔn)備使用了,而sqlrange[6]更進(jìn)一步,已經(jīng)支持了。
當(dāng)然你使用它必須下載 Go 1.22 或者 gotip, 并且設(shè)置export GOEXPERIMENT=rangefunc。
它提供了Query和Exec可遍歷函數(shù)。比如Query從一個(gè)表中查詢Point數(shù)據(jù):
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 一氣呵成。這里的資源管理是自動(dòng)的,底層的*sql.Rows遍歷完會(huì)自動(dòng)關(guān)閉。
參考文檔
[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函數(shù)使用技巧實(shí)例探究的詳細(xì)內(nèi)容,更多關(guān)于go for-range函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go語言區(qū)塊鏈學(xué)習(xí)調(diào)用智能合約
這篇文章主要為大家介紹了go語言區(qū)塊鏈學(xué)習(xí)中如何調(diào)用智能合約的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
GoLand 2020.3 正式發(fā)布有不少新功能(支持泛型)
這是 2020 年第 3 個(gè)版本,也是最后一個(gè)版本,你還將發(fā)現(xiàn)許多新的代碼編輯功能,具體內(nèi)容詳情跟隨小編看看有哪些新特性2020-12-12
golang中的io.ReadCloser與ioutil.NopCloser使用
這篇文章主要介紹了golang中的io.ReadCloser與ioutil.NopCloser使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實(shí)例分析了Go語言使用linux的系統(tǒng)命令ps來分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03
Golang基于epoll實(shí)現(xiàn)最簡單網(wǎng)絡(luò)通信框架
這篇文章主要為大家詳細(xì)介紹了Golang如何基于epoll實(shí)現(xiàn)最簡單網(wǎng)絡(luò)通信框架,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)學(xué)習(xí)2023-06-06
sublime text3解決Gosublime無法自動(dòng)補(bǔ)全代碼的問題
本文主要介紹了sublime text3解決Gosublime無法自動(dòng)補(bǔ)全代碼的問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Golang中數(shù)據(jù)結(jié)構(gòu)Queue的實(shí)現(xiàn)方法詳解
這篇文章主要給大家介紹了關(guān)于Golang中數(shù)據(jù)結(jié)構(gòu)Queue的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Go使用Google?Gemini?Pro?API創(chuàng)建簡單聊天機(jī)器人
這篇文章主要為大家介紹了Go使用Google?Gemini?Pro?API創(chuàng)建簡單聊天機(jī)器人實(shí)現(xiàn)過程詳解,本文將通過最新的gemini?go?sdk來實(shí)現(xiàn)命令行聊天機(jī)器人2023-12-12

