Funky's NoteBook

Go Core Dev Function

字数统计: 2,906阅读时长: 13 min
2019/01/17 Share

函数

  • 声明格式
1
2
3
func funcName(param-list)(result-list){
function body
}
  • 特点

    • 可以无传入参数和返回值(默认返回0)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func A() {
    //do something
    }

    func A() int {
    // do something
    ...
    return 1
    }
    • 多个相邻的相同类型的参数可以使用简写模式
    1
    2
    3
    func add(a, b int) int {
    return a + b
    }
    • 支持有名的返回值,参数名就相当于函数体内最外层的局部变量,命名返回值变量被初始化为类型零值,最后的 return 可以不带参数名直接返回
    1
    2
    3
    4
    5
    6
    7
    // sum 相当于函数内的局部变量,被初始化为零
    func add(a, b int) (sum int) {
    sum = a + b
    return // return sum 的简写
    // 如果是 sum := a + b 则相当于声明一个 sum 变量命名返回变量 sum 覆盖,
    // 最后需要显式使用 return sum
    }
    • 不支持默认值参数
    • 不支持函数重载
    • 不支持函数嵌套,严格的讲是不支持命名函数的嵌套定义,但是支持嵌套匿名函数
    1
    2
    3
    4
    5
    6
    func add(a, b int) (sum int) {
    anonymous := func (x, y int) int {
    return x + y
    }
    return anonymous(a, b)
    }

多值返回

定义多值返回的返回参数列表需要使用()包裹

1
2
3
func swap(a, b int) (int, int){
return b, a
}

习惯用法:

如果多值返回值有错误类型,则一般将错误类型作为最后一个返回值。

不定参数

  • 所有不定参数类型必须是相同的
  • 不定参数必须是函数的最后一个参数
  • 不定参数在函数体内相当于切片,对切片的操作同样适合对不定参数的操作
  • 切片可以作为参数传递给不定参数,切片名后要加上 “…”
1
2
3
4
5
6
7
8
9
10
11
12
13
func sum (arr ...int) (sum int) {
for _, v := range arr {
sum += v
}
return
}
func main(){
slice := []int{1, 2, 3, 4}
array := [...]int{1, 2, 3, 4}
// 数组不可以作为实参传递给不定参数的函数
sum(slice...)
// sum(array...) 不允许
}
  • 形参为不定参数的函数和形参为切片的函数类型不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func sum1(arr ...int) (sum int) {
for v := range arr {
sum += v
}
return
}

func sum2 (arr []int) (sum int) {
for v := range arr {
sum += v
}
return
}
func main() {
fmt.Printf("%T\n", sum1)
fmt.Printf("%T\n", sum2)
}

函数签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func add (a, b int) int {
return a + b
}

type Op func (int, int) int // 定义一个函数类型

func do(f Op, a, b int) int { //定义一个函数,第一个参数是函数类型Op
return f(a, b) // 函数类型变量可以直接用来进行函数调用
}
// 函数类型、map、slice、chan 一样,时间函数类型变量和函数名都可以当作指针变量,该指针指向函数代码的初始位置。
func main() {
a := do(add, 1, 2) //函数名 add 可以当做相同函数类型形参,不需要强制类型转换
fmt.Println(a)
}
  • 函数赋值
1
2
3
4
5
6
7
8
func sum (a, b int) int {
return a + b
}
func main () {
sum(3, 4)
f := sum
f(1, 2)
}

匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var sum = func (a, b int) int {
return a + b
}

func doinput (f func (int, int) int, a, b int) int {
return f(a, b)
}

// 匿名函数做为返回值
func wrap (op string) func(int, int) int {
switch op {
case "add":
return func (a, b int) int {
return a + b
}
case "sub":
return func (a, b int) int {
return a - b
}
default:
return nil
}
}

func main () {
//匿名函数直接被调用
defer func () {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

sum(1, 2)

// 匿名函数作为实参
doinput (func (x, y int) int {
return x + y
}, 1, 2)
opFunc := wrap("add")
re := opFunc(2, 3)
fmt.Printf("%d\n", re)
}

defer

Defer 关键字可以注册做个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
defer func (){
println("first")
}()

defer func () {
println("second")
}()
println("function body")
}
// 输出结果
// function body
// second
// first

defer 后面必须是函数或方法的调用,不能是语句,否则会报错

defer 函数实参在其注册时通过值拷贝进去,后续对实参的操作不好影响 defer 语句的输出结果。

1
2
3
4
5
6
7
8
9
10
func f() int {
a := 0
defer func(i int) {
println("defer i=", i)
}(a)
a++
return a
}

// 输出: defer i=0

defer 语句必须先注册后才能执行,如果 defer 位于 return 之后,则 defer 因为没有注册,不会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main () {
defer func () {
println("first")
}()
a := 0
println(a)
return
defer func () {
println("second")
}()
}
//结果:
// 0
// first

主动调用 os.Exit(int) 退出进程时,defer 将不再执行(即使 defer 已经提前注册)

1
2
3
4
5
6
7
8
9
10
11
12
13
package main 
import "os"
func main () {
defer func() {
println("defer")
}()
println("func body")

os.Exit(1)
}
// 结果:
// func body
// exit status 1

defer 的好处是可以在一定程度上避免资源泄露,特别是在很多 return 语句,有很多资源场景需要关闭的时候,漏掉资源关闭的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func CopyFile(dst, src string) (w int 64, err error) {
src, err := os.Open(src)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dst)

if err != nil {
return
}

defer dst.Close()

w, err = io.Copy(dst, src)
return
}

defer 语句位置不当可能导致 panic,一般 defer 语句放在错误检查语句之后。

defer 也有明显的副作用: defer 会推迟资源释放,因此 defer 尽量不要放到循环语句里,将大函数内部的 defer 语句单独拆分成一个小函数是一种很好的实践方式。

defer 中最好不要对有名返回值参数进行操作, 否则会引发意外结果。

闭包

闭包= 函数+引用环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
func fa (a int) func(i int) int {
return func (i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1) // f 引用的外部的闭包环境包括本次函数调用的形参 a 的值 1
g := fa(1)
// 此时 f、g 引用的闭包环境中的 a 不是同一个,而是 2 次函数调用产生的副本
println(f(1))
// 多次调用 f 引用的是同一个副本
println(f(1))
// g 中的 a 的值仍然是 1
println(g(1))
println(g(1))
}
/*
输出结果:
0xc00006e000 1
2
0xc00006e000 2
3
0xc00006e008 1
2
0xc00006e008 2
3
*/

f、g 引用的是不同的 a。

如果函数调用返回的闭包引用修改了全局变量,则每次调用都会影响全局变量。

使用闭包是为了减少全局变量,所以闭包引用全局变量不是好的编程方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
var (
a = 0
)
func fa () func(i int) int {
return func (i int) int {
println(&a, a)
a = a + i
return a
}
}

func main() {
f := fa() // f 引用的外部的闭包环境包括全局变量 a
g := fa()
// 此时 f、g 引用的闭包环境中的 a 是同一个
println(f(1))
println(g(1))
println(g(1))
println(g(1))
}
/* 结果
0x10d5af0 0
1
0x10d5af0 1
2
0x10d5af0 2
3
0x10d5af0 3
4
*/

同一个函数返回的多个闭包共享该函数的局部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main
func fa(base int) (func (int) int,func (int) int){
println(&base, base)
add := func (i int) int {
base += i
println(&base, base)
return base
}
sub := func (i int) int {
base -= i
println(&base,base)
return base
}

return add, sub
}

func main() {
// f、g 闭包引用的 base 是同一个,是 fa 函数调用传递过来的实参值
f, g := fa(0)

// s、k 闭包引用的 base 是同一个,是 fa 函数调用传递过来的实参值
s, k := fa(0)

// f、g 和 s、k 引用不同的闭包变量,这是由于 fa 每次调用都要重新分配形参

println(f(1), g(2))
println(s(1), k(2))
}
/*
0xc00006e000 0
0xc00006e008 0
0xc00006e000 1
0xc00006e000 -1
1 -1
0xc00006e008 1
0xc00006e008 -1
1 -1
*/

闭包的价值

减少全局变量,函数调用过程中隐式传递共享变量,但是这种方式的坏处是不够直接,不够清晰,除非是非常有价值的地方,一般不建议用闭包。

对象是附有行为的数据,而闭包是附有数据的行为,类在定义时显式集中定义了行为,但是闭包中的数据没有显示的集中声明的地方,这种数据和行为耦合的模型不是一种推荐的编程模型,仅仅锦上添花,不是不可缺少。

panic & recover

这两个内置函数用于处理 go 运行时错误。panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。

  • panic

发生 panic 后,程序会从调用调用 panic 函数位置或发生 panic 地方立即返回,逐层向上执行 defer 语句,然后逐层打印函数调用堆栈,直到被 recover 捕获或运行到最外层函数而退出。

panic 函数参数为一个空接口类型 interface {},所以任意类型的变量都可以传递给 panic,调用 panic 的方法非常简单:panic(xxx)

panic 不但可以在函数正常流程中抛出, 在 defer 逻辑里也可以再次调用 panic 或抛出 panic。defer 里面的 panic 能够被后续执行的 defer 捕获。

  • recover

recover() 用于捕获 panic,阻止 panic 继续向上传递。recover() 和 defer 一起使用,但是 recover() 只有在 defer 后面的函数体内被直接调用才能捕获 panic 终止异常,否则返回 nil,异常继续向外传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 捕获失败案例
defer recover()
defer fmt.Println(recover())
defer func() {
func() {
println("defer inner")
recover()
}()
}
// 捕获成功案例
defer func() {
println("defer inner")
recover()
}()

func except() {
recover()
}

func test() {
defer except()
panic("test panic")
}

可以有连续多个 panic 被抛出,连续多个panic 的场景只能出现在延迟调用里面,否则捕获出现连续多个 panic 被抛出的场景。但只有最后一次 panic 才能被捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main 
import "fmt"
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

// 只有最后一次 panic 调用能够被捕获
defer func() {
panic("first defer panic")
}()

defer func() {
panic("second defer panic")
}()
panic("main body panic")
}

包中的 init 函数引发的 panic 只能在 init 函数中捕获,在 main 中无法捕获,原因是 init 函数先于 main 执行,函数并不能捕获内部新启动的 goroutine 所抛出的 panic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"time"
)

func do() {
// 这里并不能捕获 da 函数中的 panic
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
go da()
go db()
time.Sleep(3 * time.Second)
}
func da() {
panic("panic da")
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}

func db() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
func main() {
do()
}

使用场景:

  • 程序遇到无法正常执行下去的错误,主动调用 panic 结束程序运行
  • 调试程序时,主动调用 panic 实现 快速退出,panic 打印出的堆栈能够更快速定位错误。
CATALOG
  1. 1. 函数
    1. 1.1. 多值返回
    2. 1.2. 不定参数
    3. 1.3. 函数签名
    4. 1.4. 匿名函数
    5. 1.5. defer
    6. 1.6. 闭包
      1. 1.6.1. 闭包的价值
    7. 1.7. panic & recover