go 语言函数 和 defer

go中的函数无须前置声明
go中的函数不支持默认参数
go中的函数不支持重载
函数定义
func name(parameter-list) (result-list) {
    // body
}

func test(x, y int32) int32 {
    // body
}

实参是按值传递的,函数接收到的是每一个实参的副本,修改函数的形参并不会影响到调用者的提供的实参,但是提供的实参包含了引用类型,比如:指针slicemap函数或者通道,那么就可以修改实参

go中的函数是第一类对象

go 中的函数对象属于第一类对象(first-class object 指可在运行期创建,可用作函数参数或返回值,可存入变量的实体),具备相同签名(参数及返回值)的视作同一类型

func hello() {
   println("hello world")
}

func exec (f func) {
    f()
}

func main() {
    f := hello
    exec(f)
}
  • 定义函数类型
    可以使用type定义函数类型

    type FormatFunc(string, ...interface{}) (string, error)
    
    func format(f FormatFunc, s string, a ...interface{}) (string, error) {
        return f(s, a...)
    }
    
  • 函数也只能判断其为nil,不支持其他比较操作

    func a() {
    
    }
    
    func b() {
    
    }
    
    func main() {
        fmt.Println(a == nil)    // 正确
        fmt.Println(a == b)      // 编译错误
    }
    
包级别函数

Go 语言没有用 public、private 这样的修饰符来修饰函数是公有还是私有,而是通过函数名称的大小写来代表,这样省略了烦琐的修饰符,更简洁:

  • 函数名称首字母小写代表私有函数,只有在同一个包中才可以被调用
  • 函数名称首字母大写代表公有函数,不同的包也可以调用
返回局部变量的指针是安全的
func test() *int {
    a := 100
    return &a
}

func main() {
    var a *int = test()
}

从函数返回局部变量的指针是安全的,编译器会通过逃逸分析来决定该变量是否在堆上分配内存。

变长参数

在参数列表最后的类型名称之前使用省略号...表示声明一个变长参数,调用整个函数的时候可以传递该类型任意数目的参数

func sum(vals ...int) int {
  total := 0
  for _, val := range vals {
      total += val
  }
  return total
}

sum()
sum(1, 2, 3)

但是如果想要传递一个切片,则必须显示写下省略号,将切片展开。

values := []int{1, 2, 3, 4}
sum(values...)
多返回值

go 函数还支持多返回值,多返回值需要用()括起来

func foo() (bool, error) {
    ...
}
命名返回值

go 函数还支持对返回值进行命名:它可以让函数更加清晰,同时也会改善帮助文档和代码编辑器提示

func paging(sql string, index int) (count int, page int, err error) {
    ...
    return
}

但是命名返回值可能会不被不同层级的同名变量遮蔽:

func add(x, y int) (z int) {
    {
        z := x + y  // 这里的 z 遮蔽了命名返回值
        return   
    }
    return
}

什么时候使用命名返回值,什么时候不需要?

  • 如果返回值类型能够明确表明其含义,就尽量不要对其命名
    func newUser() (*User, error)
    
匿名函数
strings.Map(fybc(r rune) rune { return r + 1 }, "ABC")  // 输出 BCD

函数字面量就像函数声明,但func关键字后面没有函数的名称,它是一个表达式,它的值叫作匿名函数,类似于其他语言的 lambda 表达式

func squares() func() int {
    var x int = 0
    return func() int {
        x++
        return x*x
    }
}

f := squares()  
fmt.Println(f())    // 输出 1
fmt.Println(f())    // 输出 4
fmt.Println(f())    // 输出 9

上面的例子演示了函数变量不仅仅是一段代码还可以拥有状态,里层的匿名函数能够获取和更新外层函数的局部变量。

延迟调用 defer

一个defer语句就是一个普通的函数或者方法调用,在调用之前加上,参数值会在注册时被复制并缓存起来,但实际调用会推迟到当前函数执行结束后才执行,且无论该函数是否正常执行完毕。defer语句通常用于:
释放资源:

m.mutex.Lock()
defer m.mutex.Unlock()

流程控制:

var wg wait.Group
defer wg.Wait()

异常处理:

defer func()  {  recover()  }()

使用defer函数有以下注意:

  • defer 函数的参数在defer 语句出现时就已经确定

    func foo() {
        i := 0
        defer fmt.Println(i)
        i++
        return
    }
    

    输出 i 的值为0

    不过需要注意和匿名函数配合使用的区别:

    func foo() {
        i := 0
        defer func() {  fmt.Println(i)  }()
        i++
        return
    }
    

    输出 i 的值为1

  • defer 语句没有限制使用次数,执行的时候,以调用defer语句顺序倒序进行。

    func main() {
        defer fmt.Println("a")
        defer fmt.Println("b")
    }
    // 输出:
    b
    a
    
  • defer 中可以更新命名返回值

    func test() (z int) {
        defer func() {
            fmt.Println("defer: ", z)
            z += 100
        }()
    
        return 100
    }
    
    func main() {
        fmt.Println("test: " test())
    }
    // 输出:
    defer: 100
    test: 200
    

    说明,是先完成 z = 100,再执行defer中的 z+=100,最后执行ret汇编指令。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 函数 Go语言函数格式func 函数名称(形参列表)(返回值列表){函数语句} Go语言函数的类型没有返回值没有形...
    喝酸奶要舔盖__阅读 406评论 0 0
  • 函数包含函数名、行参列表、函数体和返回值列表,使用func进行声明,函数无参数或返回值时则形参列表和返回值列表省略...
    imsilence阅读 211评论 0 0
  • 函数 Go语言和C语言一样也有函数的概念, Go语言中函数除了定义格式和不用声明以外,其它方面几乎和C语言一模一样...
    极客江南阅读 830评论 3 6
  • 1. 概述 函数是将实现单一或者相关联功能的代码组织起来,内部实现具有封闭性的代码集合,函数可以提高应用程序的模块...
    楚江云阅读 870评论 0 1
  • 只描述和C语言中函数的区别 Go语言函数不需要声明 Go语言的函数是一等公民,和变量一样,可以赋值给某一变量(匿名...
    AuglyXu阅读 483评论 0 0