本文学习Go语言函数常识。函数是根本的代码块,用于执行一个工作。在Go语言中,函数能够接管数量不固定的参数,也能够返回多个后果。

函数构造

在编程畛域,函数向编译器和开发者提供了无关的信息,这些信息指明了函数该接管什么样的输出以及会产生什么样的输入。这些信息是通过函数第一行提供的,第一行称为函数签名。

Go语言申明函数语法如下:

func 函数名称(参数名 参数类型) (返回值名称 返回值类型) {  // 函数体  return语句}
  1. 参数名在参数类型后面,如a int,这一点和其余语言是不同的
  2. 函数参数数量能够不固定,然而只容许最初一个参数数量不固定,而且必须是同种类型
  3. 返回值名称不是必须的,然而参数名是必须写的
  4. 有返回值的函数,函数体内必须蕴含return语句

示例:函数定义与调用

package mainimport "fmt"func sum(a, b int) int {    return a + b}func main() {    fmt.Printf("1+2=%d\n", sum(1, 2))}

在Go语言中,如果多个参数或多个返回值类型雷同,只须要在最初一个参数或返回值申明类型。

例如上面的函数签名在Go语言中是非法的。

func sum2(a, b int) (c, d int) 

不定参数函数

不定参数也就是数量不固定的参数。例如C语言中的printf函数就是一个典型的不定参数函数。Go语言反对不定参数函数,然而不定参数的类型必须雷同。要申明不定参数,须要应用3个点(...)。

示例:不定参数的加法函数

package mainimport "fmt"func sum(nums ...int) int {    total := 0    for _, n := range nums {        total += n    }    return total}func main() {    fmt.Printf("1+2+3+4=%d\n", sum(1, 2, 3, 4))}

在sum函数中,nums是一个蕴含所有参数的切片。

函数返回值

多返回值

在Go语言中,函数能申明多个返回值,在这种状况下,return能够返回多个后果。函数调用者可通过多变量申明接管多个返回值。

示例:多返回值函数

package mainimport (    "errors"    "fmt")func div(a, b int) (int, error) {    if b == 0 {        return 0, errors.New("b is zero")    }    return a / b, nil}func main() {    ret, err := div(2, 1)    if err != nil {        fmt.Println(err)        return    }    fmt.Printf("2/1=%d\n", ret)}

命名返回值

命名返回值让函数可能在返回前将返回值赋给命名变量,这种设计有利于进步程序可读性。要指定命名返回值,可在函数签名的返回值类型后面增加变量名。

示例:命名返回值函数

package mainimport (    "fmt")func sum(a, b int) (total int) {    total = a + b    return}func main() {    fmt.Printf("1+2=%d\n", sum(1, 2))}

应用命名返回值后,return关键字能够独自呈现,当然,return关键字持续返回后果值也是非法的。

func sum(a, b int) (total int) {    total = a + b    return total}

函数类型

在Go语言中,函数是一种数据类型,能够将函数赋值给变量、或者作为参数传递,也能够作为返回值返回。

示例:将函数作为变量、参数、返回值。

package mainimport "fmt"func main() {    // 函数作为变量    sum := func(a, b int) int {        return a + b    }    fmt.Printf("1+1=%d\n", sum(1, 1))    // 函数作为参数    sum2(1, 1, func(total int) {        fmt.Printf("1+1=%d\n", total)    })    // 函数作为返回值    totalFn := sum3(1, 1)    fmt.Printf("1+1=%d\n", totalFn())}func sum2(a, b int, callback func(int)) {    total := a + b    callback(total)}func sum3(a, b int) func() int {    return func() int {        return a + b    }}

匿名函数、闭包、提早执行函数

匿名函数

匿名函数指没有名称的函数,只有函数签名(参数和返回值申明)和函数体,匿名函数常常用于回调、闭包、长期函数等。

示例:利用匿名函数实现事件总线。

package mainimport "fmt"func main() {    emitter := make(map[string]func())    addEventListener(emitter, "event1", func() {        fmt.Println("event1 called")    })    emit(emitter, "event2")}// 增加事件监听器// emitter 事件总线// event 事件名// callback 回调函数func addEventListener(emitter map[string]func(), event string, callback func()) {    emitter[event] = callback}// 触发事件// emitter 事件总线// event 事件名func emit(emitter map[string]func(), event string) {    callback, ok := emitter[event]    if ok {        callback()    }}

main函数调用addEventListener时传入的第三个函数即为匿名函数。

闭包

闭包能够了解为定义在一个函数外部的函数。在实质上,闭包是函数和其援用环境的组合体。援用环境即便在内部函数执行完结也不会被回收,因而能够利用闭包保留保留执行环境。

示例:利用闭包提供惟一ID生成器。

package mainimport "fmt"func main() {    s1 := sequenceId()    s2 := sequenceId()    fmt.Printf("s1 -> %v\n", s1())    fmt.Printf("s1 -> %v\n", s1())    fmt.Printf("s2 -> %v\n", s2())    fmt.Printf("s2 -> %v\n", s2())}func sequenceId() func() int {    var id int    return func() int {        id++        return id    }}

输入如下

s1 -> 1s1 -> 2s2 -> 1s2 -> 2

函数sequenceId定义了一个局部变量id,并返回了一个子函数,子函数外部拜访了内部的id,因而这形成一个典型的闭包。在后面的内容中咱们学习过变量作用域,外部总是能够拜访内部的变量或常量,而内部无法访问外部的变量或常量。此外,因为变量id被子函数应用,因而在sequenceId函数返回后,id也不会被销毁。

每调用一次sequenceId函数都会返回一个新的子函数以及对应的id,因而s1和s2之间的输入互不影响。

留神:因为闭包会导致被援用的变量无奈销毁,因而须要留神应用,防止产生内存透露。

提早执行函数

在理论编程中往往会关上一些资源,例如文件、网络连接等等,这些资源在应用结束时(无论是失常敞开或者函数异样)须要被动敞开,当函数的完结分支太多或者逻辑比较复杂时容易产生遗记敞开的状况导致资源透露。

Go语言提供了defer关键字用来提早执行一个函数,个别应用该函数提早敞开资源。多个defer语句会依照先进后出的形式执行,也就是最初申明的最先执行,典型的栈构造。

示例:defer执行程序。

package mainimport "fmt"func main() {    defer f1()    defer f2()    fmt.Println("call main")}func f1() {    fmt.Println("call f1")}func f2() {    defer fmt.Println("defer call f2")    fmt.Println("call f2")}

输入如下

call maincall f2defer call f2call f1
  1. 第一行输入call main是因为main函数中只有一个非defer语句,因而call main最先执行
  2. 第二行输入call f2是因为f2函数外部有一个非defer语句
  3. 第三行输入defer call f2是因为f2函数的fmt.Println("call f2")执行结束后能力执行defer
  4. 第四行输入call f1是因为defer f1()最先申明因而最初执行

示例:基于defer和闭包结构一个函数执行耗时记录器。

package mainimport (    "fmt"    "time")type Person struct {    Name string    Age  int    Sex  string}func main() {    defer spendTime()()    time.Sleep(time.Second)    fmt.Println("call main")}func spendTime() func() {    startAt := time.Now()    return func() {        fmt.Println(time.Since(startAt))    }}

输入如下

call main1.002345498s

spendTime()会返回一个闭包,因而定义defer时会初始化startAt为以后工夫,defer执行时会执行闭包函数失去函数耗时。main函数为了测试不便休眠了一秒钟,因而能够看到输入是超过1秒的。

小结

本文介绍了如何在Go语言中应用函数。包含不定参数函数、多返回值和命名返回值函数以及将函数作为类型应用的办法,最初介绍了匿名函数、闭包和提早执行函数。接下来的内容中将介绍Go语言中的构造体。