Eli's Blog

1. 函数

  • 不支持嵌套、重载和默认参数
  • 无需声明原型、不定长度变参、多返回值、命名返回参数、匿名函数、闭包
  • 本身就是一种类型

函数调用底层分析:

  • 栈区:基本数据类型一般分配到栈区。编译器存在一个逃逸分析。每个函数有独立的栈,函数执行完毕,自动销毁
  • 堆区:引用数据类型一般分配在堆区
  • 代码区:存放代码指令

init()函数:每个源文件,都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。

  • 执行顺序:全局变量定义 -> init() -> main()

1.1 参数传递

不管是指针、引用类型,还是其他类型参数,都是值拷贝传递(pass-by-value)。区别无非是拷贝目标对象,还是拷贝指针对象本身而已。

在函数调用时,会为形参和返回值分配内存空间,并将实参拷贝到形参的内存。

1.2 参数过多,改用struct

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
type serverOption struct {
ip string
port int
path string
timeout time.Duration
log *log.Logger
}

func newOption() *serverOption {
return &serverOption{
ip: "0.0.0.0",
port: 8080,
path: "/data/www",
timeout: time.Second*5,
log: nil,
}
}

func server(option *serverOption) {}

func main() {
opt := newOption()
opt.port = 8080

server(opt)
}

1.3 变参

变参,实际上传递的是一个slice,如果是array,先转化为slice。s := a[:]...

1
2
3
4
5
6
7
8
9
10
func test(a ...int) {
fmt.Printf("%T, %v\n", a, a)
}

func main() {
test(1, 2, 3, 4)

a := [3]int {10, 20, 30}
test(a[:]...)
}

2. 匿名函数

2.1 直接执行

1
2
3
4
5
func main() {
func (s string) {
println(s)
} ("Hello world")
}

2.2 赋值给变量

1
2
3
4
5
6
7
func main() {
add := func (x, y int) int {
return x + y
}

println(add(2, 3))
}

2.3 作为参数

1
2
3
4
5
6
7
8
9
func test(f func()) {
f()
}

func main() {
test(func() {
println("Hello world")
})
}

2.4 作为返回值

1
2
3
4
5
6
7
8
9
10
func test(x, y int) func() int {
return func() int {
return x + y
}
}

func main() {
add := test(2, 3)
println(add())
}

2.5 作为结构体字段

1
2
3
4
5
6
7
8
9
10
11
12
13
func testStruct() {
type calc struct {
mul func(x, y int) int
}

z := calc {
mul: func(x, y int) int {
return x * y
},
}

println(z.mul(2, 5))
}

2.6 通过Channel传递

1
2
3
4
5
6
7
8
9
func testChannel() {
c := make(chan func(int, int) int, 2)

c <- func(a int, b int) int {
return a + b
}

println((<- c)(1, 2))
}

3. 闭包(closure)

闭包:一个函数和与其相关的引用变量组成的一个实体

  • 返回一个匿名函数
  • 该匿名函数使用了函数外变量

3.1 示例1

1
2
3
4
5
6
7
8
9
10
func test(x int) func() {
println(&x)
return func() {
println(&x, x) // 返回的函数,包含了x环境上下文
}
}

func main() {
test(5)()
}
1
go build -gcflags "-N -l" main.go

3.2 示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
f := closure(10)
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 12
}

func closure(x int) func(int) int {
fmt.Printf("%p\n", &x) // 0xc0000140b0
return func(y int) int {
fmt.Printf("%p\n", &x) // 0xc0000140b0
return x + y
}
}

3.3 多匿名函数返回,延迟求值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func test() []func() {
var fs []func()

for i := 0; i < 3; i++ {
fs = append(fs, func() {
println(&i, i) // 延迟执行特性,最后都输出3
})
}

return fs
}

func main() {
for _, f := range test() {
f()
}
}

修正后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func test() []func() {
var fs []func()

for i := 0; i < 3; i++ {
x := i // 立即赋值
fs = append(fs, func() {
println(&x, x)
})
}

return fs
}

func main() {
for _, f := range test() {
f()
}
}

4. 递归函数

4.1 阶乘

1
2
3
4
5
6
7
8
9
10
11
12
func factorial(n uint64) uint64 {
if n > 0 {
return n * factorial(n - 1)
}

return 1
}

func main() {
var i int = 15
fmt.Printf("%d 的阶乘等于 %d", i, factorial(uint64(i)))
}

4.2 Fibonacci

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func fibonacci(n uint64) uint64 {
if n < 2 {
return n
}

return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", fibonacci(uint64(i)))
}
fmt.Println()
}

5. 延迟调用(defer)

  • FILO 先进后出
  • 即使函数发生panic错误,也会执行
  • 支持匿名函数调用
  • 用于资源清理、文件关闭、解锁以及记录时间等操作
  • 与匿名函数配合,可在return后修改函数的计算结果

5.1 示例1

1
2
3
4
5
6
7
8
9
10
11
func main() {
for i := 0; i < 3; i++ {
defer fmt.Println(i) // 2 1 0
}

for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i) // 3 3 3
}()
}
}

5.2 循环中使用延迟调用

延迟调用在函数结束时调用,如果将其放到循环中,会造成资源浪费

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("./log/%d.txt", i)

f, err := os.Open(path)
if err != nil {
log.Println(err)
continue
}

defer f.Close()

// do something
}
}

优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
do := func(i int) {
path := fmt.Sprintf("./log/%d.txt", i)

f, err := os.Open(path)
if err != nil {
log.Println(err)
return
}

defer f.Close()

// do something
}

for i := 0; i < 1000; i++ {
do(i)
}
}

5.3 延迟调用闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
var fs = [4]func(){}

for i := 0; i < 4; i++ {
defer fmt.Println("defer i = ", i)
defer func() {
fmt.Println("defer_closure i = ", i) // always 4
}()
defer func(i int) {
fmt.Println("defer_closure i = ", i) // i will change
}(i)
fs[i] = func() {
fmt.Println("closure i = ", i)
}
}

for _, f := range fs {
f()
}
}

5.4 延迟调用性能

相比直接用CALL汇编指令调用函数,延迟调用则须花费更大代价。这其中包括注册、调用等操作,还有额外的缓存开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var m sync.Mutex

func call() {
m.Lock()
m.Unlock()
}

func deferCall() {
m.Lock()
defer m.Unlock()
}

func BenchmarkCall(b *testing.B) {
for i := 0; i < b.N; i++ {
call()
}
}

func BenchmarkDeferCall(b *testing.B) {
for i := 0; i < b.N; i++ {
deferCall()
}
}

5.5 for range与闭包的坑

1
2
3
4
5
6
7
8
9
10
11
func main() {
s := []string{"a", "b", "c"}

for _, v := range s {
go func() {
fmt.Println(v) // 三次全部打印"c"
}()
}

select {} // 使main一直等待直到deadlock异常
}

6. 错误处理

标准库错误接口:

1
2
3
type error interface {
Error() string
}

6.1 panic & recover

  • panic: 主动抛出错误
  • recover: 捕获panic抛出的错误
1
2
func panic(v interface{})
func recover() interface{}

panic和recover运行机制:

1) 引发panic有两种情况:一是程序主动调用,二是程序产生运行时错误(Runtime Error),由运行时检测并退出

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

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

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

注意:除非是不可恢复性、导致系统无法正常工作的错误,否则不建议使用panic。如:文件系统没操作权限、服务端口被占用、数据库未启动等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
result := div(8, 0)
fmt.Println(result)
}

func div(a int, b int) int {
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
}
}()

return a / b
}

6.2 主动panic并捕获

1
2
3
4
5
6
7
8
9
10
func main() {
defer func() {
if err := recover(); err != nil {
log.Fatalln(err)
}
}()

panic("crash")
println("exit.")
}

6.3 无效捕获和有效捕获错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 无效的捕获
defer recover()
defer fmt.Println(recover())
defer func() {
func() {
recover() // 嵌套多层,无效
}()
}()

// 有效的捕获
defer func() {
recover()
}()

func except() {
recover()
}

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

6.4 多个panic,只会捕获最后一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err) // three 只会捕获最后一个
}
}()

defer func() {
panic("three")
}()

defer func() {
panic("two")
}()

panic("one")
}

7. 自定义错误

  • errors.New("错误描述"):返回一个error类型的值,表示一个错误
  • panic内置函数:接收一个interface{}类型的值(即任意值)作为参数,可以接受error类型的变量,输出错误信息,并退出程序。

7.1 负数平方根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func Sqrt(x float64) (float64, error) {
if x < 0 {
return 0, errors.New("math: square root of negative number")
}

return math.Sqrt(x), nil
}

func main() {
result, err := Sqrt(-1)

if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}