Article

Golang 基础 3

闭包案例 -> 函数 makeCounter

package main

import "fmt"

func main() {
	makeCounter := func() func() int {
		count := 0
		return func() int {
			count++
			return count
		}
	}

	counter := makeCounter()
	fmt.Println(counter())
	fmt.Println(counter())
}

alt text

makeCounter是一个外层函数,内部有一个匿名函数,它的返回值是count,而makeCounter本身的返回值正是这个匿名函数。

当匿名函数开始执行时,makeCounter销毁,那么count本应该销毁,但是却在内部函数里调用,于是就形成了闭包。

总的来说,闭包就是:闭包 = 函数 + 它引用的外部变量。

延迟调用

当有多个 defer 描述的函数时,就会像栈一样先进后出的顺序执行。

package main

import "fmt"

func main() {
	fmt.Println(0)
	Do()
}

func Do() {
	defer fmt.Println(1)
	fmt.Println(2)
	defer fmt.Println(3)
	defer fmt.Println(4)
	fmt.Println(5)
}

alt text

延迟调用通常用于释放文件资源,关闭网络连接等操作,还有一个用法是捕获panic,不过这是错误处理一节中才会涉及到的东西。

defer不推荐用于循环当中。因为没创建一个defer,都要在当前的协程里创建一片内存空间。

如果循环过程涉及的不是简单的过程,而是一个较为复杂的数据处理流程时,当外部的请求数激增时,那么在短时间内就会创建大量的defer,在循环次数很大的循环当中,就会导致占用内存激增,也就是内存泄漏。

defer和闭包可以扯上关系,如下列展示:

func main() {
  var a, b int
  a = 1
  b = 2
  defer fmt.Println(sum(a, b))
  a = 3
  b = 4
}

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

这是用defer实现的,结果输出为3而不是7。

所以我们可以知道:defer捕获的是只拷贝,也就是看到声明时的参数值。

func main() {
  var a, b int
  a = 1
  b = 2
  f := func() {
    fmt.Println(sum(a, b))
  }
  a = 3
  b = 4
  f()
}

使用闭包组合,结果输出就是7,因为闭包捕获的是a和b的变量本身,也就是类似指针的效果,那么闭包就会去实时查找a和 b,像一个实时监控摄像头一样,调用时采取看此刻的a和b是多少。

options模式

这是一个结构体。

type Person struct {
	Name     string
	Age      int
	Address  string
	Salary   float64
	Birthday string
}

我们可以采用类似映射的方法来构造一个结构体。

xiaooming := Person{
	Name:     "xiaoming",
	Age:      18,	
	Address:  "地球",
	Salary:   10000000,
	Birthday: "13月32日",
}

另外,我们用PersonOptions类型来构造函数。

type PersonOptions func(p *Person)

func WithName(name string) PersonOptions {
	return func(p *Person) {
		p.Name = name
	}
}

func WithAge(age int) PersonOptions {
	return func(p *Person) {
		p.Age = age
	}
}

func WithAddress(address string) PersonOptions {
	return func(p *Person) {
		p.Address = address
	}
}

func WithSalary(salary float64) PersonOptions {
	return func(p *Person) {
		p.Salary = salary
	}
}

func NewPerson(options ...PersonOptions) *Person {
	// 优先应用options
	p := &Person{}
	for _, option := range options {
		option(p)
	}

	// 默认值处理
	if p.Age < 0 {
		p.Age = 0
	}

	return p
}

这样,对于不同实例化的需求,只需要一个构造函数就可以完成:

func main() {
  pl := NewPerson(
    WithName("John Doe"),
    WithAge(25),
    WithAddress("123 Main St"),
    WithSalary(10000.00),
  )

  p2 := NewPerson(
    WithName("Mike jane"),
    WithAge(30),
  )
}