乐趣区

关于golang:Golang程序设计函数

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

函数构造

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

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

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

示例:函数定义与调用

package main

import "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 main

import "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 main

import (
    "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 main

import ("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 main

import "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 main

import "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 main

import "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 -> 1
s1 -> 2
s2 -> 1
s2 -> 2

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

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

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

提早执行函数

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

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

示例:defer 执行程序。

package main

import "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 main
call f2
defer call f2
call 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 main

import (
    "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 main
1.002345498s

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

小结

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

退出移动版