Go语言学习笔记 - 函数

技术学习 · · 1284 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

函数

  不支持 嵌套 (nested)、重载 (overload) 和 默认参数 (default parameter)。
• 无需声明原型。
• 支持不定长变参。
• 支持多返回值。
• 支持命名返回参数。
• 支持匿名函数和闭包。
使用关键字 func 定义函数,左大括号依旧不能另起一行。

func test(x,y int,s string) (int string) { //类型相同的相邻参数可合并。
    n := x + y //多值返回必须用括号
    return n, fmt.Sprintf(s,n)
}

  函数是第一类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读。

func test(fn func() int) int {
    return fn()
}

type FormatFunc func(s string, x,y int) string //定义函数类型

func format(fn FormatFunc, s string, x,y int) string {
    return fn(s,x,y)
}

func main() {
    s1 := test(func() int { return 100}) //直接将匿名函数当参数

    s2 := format(func(s string, x, y int) string {
        return fmt.Sprintf(s, x, y)
    }, "%d, %d", 10, 20)
    println(s1, s2)

变参

  变参本质上就是slice。只能有一个,且必须是最后一个参数位。

func test(s string, n ...int) string {
    var x int
    for _, i := range n {
        x += i
    }
    return fmt.Sprintf(s, x)
}

func main() {
    println(test("sum: %d", 1, 2, 3))
}

  使用slice对象做变参时,必须展开。

func main() {
    s := []int{1, 2, 3}
    println(test("sum: %d",s...))
}

返回值

  不能用容器对象接收多返回值。只能用多个变量,或“_”忽略。多个返回值可直接作为其他函数调用实参。

func test() (int, int) {
    return 1, 2
}

func add(x, y int) int {
    return x + y
}

func sum(n ...int) int {
    var x int
    for _, i := range n {
        x += i
    }
    return x
}

func main() {
    println(add(test()))
    println(sun(test()))
}

  命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。

func add(x, y int) (z int) {
    z = x + y
    return
}
func main() {
    println(add(1, 2))
}

  命名返回参数可被同名局部变量遮蔽,此时需要显式返回。

func add(x, y int) (z int) {
  {// 不能在一个级别,引发 "z redeclared in this block" 错误。
      var z = x + y
      // return // Error: z is shadowed during return
      return z // 必须显式返回。
  }
}

  命名返回参数允许 defer 延迟调用通过闭包读取和修改。

func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()
    z = x + y
    return
}

func main() {
    println(add(1, 2)) //输出:103
}

  显式 return 返回前,会先修改命名返回参数。

func add(x, y int) (z int) {
    defer func() {
        println(z)  //输出:203
    }()

    z = x + y
    return z + 200
}

func main() {
    println(add(1, 2))  //输出:203
}

  匿名函数可赋值给变量,做为结构字段,或者在 channel 里里传送。闭包复制的是原对象指针,这就很容易解释延迟引用现象。

延迟调用

  关键字 defer 用于注册延迟调用。这些调用直到 ret 前才被执行,通常用于释放资源或错误处理。多个 defer 注册,按 FILO 次序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。

var log sync.Mutex
func test() {
    lock.Lock()
    lock.Unlock()
}
func testdefer() {
    lock.Lock()
    defer lock.Unlock()
}
func BenchmarkTest(b *testging.B) {
    for i := 0; i < b.N; i++ {
        test()
  }
}
func BenchmarkTestDefer(b *testging.B) {
    for i := 0; i < b.N; i++ {
        testdefer()
  }
}
//输出:
BenchmarkTest    50000000  43 ns/op
BenchmarkTestDefer 20000000  128 ns/op

错误处理

  没有结构化异常,使用panic抛出错误,recover捕获错误。由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未
捕获的错误都会沿调用堆栈向外传递。

func test() {
  defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // 将interface{}转型为string类型
    }()
    panic("panic erro!")
}

  除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。如何区别使用 panic 和 error 两种方式?

惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:技术学习

查看原文:Go语言学习笔记 - 函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1284 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传