Go 函数进阶

函数值

函数也可以当作值来使用,一个简单的例子:

func square(n int) int { return n * n }

func main() {

f := square

fmt.Println(f(3)) // "9"

}

函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。一个将函数值传递给函数的例子:

func TryTimes(ctx context.Context, tryTime int, duration time.Duration, dofunc func() error) (err error) {

if tryTime < 1 && tryTime > 5 {

return fmt.Errorf("Error TryTime")

}

for i := 0; i < tryTime; i++ {

err = dofunc()

logs.CtxInfo(ctx, "try No.%v time err: %v", i, err)

if err == nil {

break

}

time.Sleep(duration)

}

return err

}

匿名函数

通过func关键字后面不带函数名的方式,我们可以定义匿名函数,比如用在上面定义的TryTimes函数中:

err = TryTimes(ctx, 3, 0, func() error {

err := rpc.Call(...)

return err

})

后面的defer语句中,也可以用到匿名函数,将匿名函数放在defer的后面。

匿名函数中如果捕获了外部变量,称为闭包(closure),闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。

有两种匿名函数的写法需要注意一下:

var i int

defer fmt.Println(i) // 即刻确定i

defer func() {

fmt.Println(i) // 运行结束捕获i

}

i += 1

再看一个例子加深理解:

func Increase() func() int {

n := 0

return func() int { // 这里返回了一个闭包函数,该函数访问了外部变量n

n++

return n

}

}

func main() {

in := Increase()

fmt.Println(in()) // 1

fmt.Println(in()) // 2

}

变长参数

在声明函数时,通过在参数类型前面加上...可以让函数接收任意数量的该类型参数,在golang的fmt.Sprintfappend,gorm的db.Where()中都使用了这种方式。一个例子:

func sum(vals ...int) int {

total := 0

for _, val := range vals {

total += val

}

return total

}

defer

defer中的内容可以在函数正常结束返回(return)或者函数产生panic异常结束的时候得到执行,这一机制可以让我们方便地进行一些资源的释放,或者捕获panic异常,因为不管在函数执行过程中发生了什么,defer中的内容总是确保可以得到执行。

在没有defer的情况下,我们需要小心地处理每一次错误返回以及异常处理,确保在函数返回时能够同时将开启的资源释放掉,这就意味着我们要在很多地方写上释放资源的语句。随着函数越来越复杂,维护清理逻辑将变得越来越困难。而使用defer之后,我们只需要在开启资源的时候同时加上一条defer语句回收资源,就能保证资源得到释放。

defer常用的场景举例:

// 加锁之后释放锁

var mu sync.Mutex

mu.Lock()

defer mu.Unlock()

// 关闭数据库链接

rows := *sql.Rows

defer rows.Close()

// 释放文件资源

f, _ := os.Open(filename)

defer f.Close()

当然,defer 还有一个常见用法是用来捕获运行时发生的panic异常,见下文。

在函数中可以多次使用 defer 语句,最终这些defer语句的执行顺序按照先入后出(FILO)的原则,即先声明的后执行。

defer 的执行时机是在 return 之前,看如下两个例子:

func testDeferReturn() int {

a := 1

defer func() {

a = 2

}()

return a // 返回 1

}

func testDeferReturn1() (ret int) {

ret = 1

defer func() {

ret = 2

}()

return ret // 返回 2

}

捕获异常

函数运行过程中有可能会发生 panic,比如数组的索引越界,或者尝试访问一个未初始化的值为 nil 的 map,或者尝试访问某个值为nil的结构体中的一些元素。如果这时不想让进程崩溃,可以使用 recover 捕获异常:

func DoSomething() (err error) {

defer func() {

if p := recover(); p != nil {

err = fmt.Errorf("internal error: %v", p)

}

}()

/*

...

*/

}

通过 runtime.Stack,我们可以获取完整的堆栈调用信息,帮助我们更好地定位问题:

import "runtime/debug"

func DoSomething() (err error) {

defer func() {

if p := recover(); p != nil {

err = fmt.Errorf("internal error: %v", p)

logs.Printf("fatal panic, error: %v, stack: %v", p, debug.Stack())

}

}()

/*

...

*/

}

以上是 Go 函数进阶 的全部内容, 来源链接: utcz.com/z/264395.html

回到顶部