Go 函数进阶

发布于 2022-01-13 13:08:33 字数 3577 浏览 1137 评论 0

函数值

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

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())
    }
  }()
  /*
  ...
  */
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文