Go 语言程序设计——过程式编程(5)

闭包

  • 闭包就是一个函数 “捕获” 了和它在同一作用域的其他常量和变量
  • 闭包被调用的时候,不管在程序说明地方调用,闭包能够使用这些常量或者变量
  • 闭包不关心这些捕获了的变量和常量是否超出了作用域,所以只要闭包还在使用,变量就还存在
  • Go 语言中,所有匿名函数(Go 语言规范中称之为函数字面量)都是闭包
  • 闭包和普通函数的区别是闭包没有名字(所以 func 关键字后面紧接着左括号)
  • 闭包的一种用法是利用包装函数来为被包装的函数预定义一到多个参数
  • 例子(给大量文件增加不同的后缀):
addPng := func(name string) string { return name + ".png" }
addJpg := func(name string) string { return name + ".jpg" }
fmt.Println(addPng("filename"), addJpg("filename"))
  • 现实环境中我们常用到一个工厂函数(factory function),工厂函数返回一个函数:
func MakeAddSuffix(suffix string) func(string) string {
  return func(name string) string {
    if !strings.HasSuffix(name, suffix) {
      return name + suffix
    }
    return name
  }
}

递归函数

  • 递归函数就是调用自己的函数
  • 互相递归函数就是互相调用对方的函数
  • 递归函数通常有相同的结构:一个跳出条件一个递归体
  • 跳出条件就是一个条件语句,例如 if 语句,根据传入的参数判断是否需要停止递归
  • 递归体是函数自身所做的一些处理,包括最少也得调用自身一次(调用它互相递归的另一个函数体)
  • 递归调用时所传入的参数一定不能和当前函数传入的一样,在 跳出条件 里还会检查是否可以结束递归
  • 递归函数非常便于实现递归的数据结构例如 二叉树
  • 递归函数对于数值计算而言可能会性能比较低下
  • 常规递归函数例子:
for n := 0; n < 20; n++ {
  fmt.Print(Fibonacci(n), " ")
}
fmt.Println()

func Fibonacci(n int) int {
  if n < 2 {
    return n
  }
  return Fibonacci(n - 1) + Fibonacci(n - 2)
}
  • 互相递归函数的例子:
females := make([]int, 20)
males := make([]int, len(females))
for n := range females {
  females[n] = HofstadterFemale(n)
  males[n] = HofstadterMale(n)
}
fmt.Println("F", females)
fmt.Println("M", males)

func HofstadterFemale(n int) int {
  if n <= 0 {
    return 1
  }
  return n - HofstadterMale(HofstadterFemale(n - 1))
}
func HofstadterMale(n int) int {
  if n <= 0 {
    return 0
  }
  return n - HofstadterFemale(HofstadterMale(n - 1))
}
  • 函数开始处都有一个 跳出条件 用来确保递归能够正常结束,在递归发生的地方我们需要保证最终 跳出条件 会被满足
  • Go 语言不需要预定义函数,Go 语言允许函数以任何顺序定义
  • 能用循环的话,最好使用循环,循环的好处是可以减少调用的开销

运行时选择函数

  • Go 语言中,函数属于第一类值,也就是说,我们可以将它保存在一个变量里,这样我们可以决定要执行哪一个函数
  • 同一个函数可以有两个或者多个不同的实现,在使用的时候创建就行了
使用映射和函数引用来制造分支
  • 例子:
var FunctionForSuffix = map[string]func(string) ([]string, error) {
  ".gz": GzipFileList,
  ".tar": TarFileList,
  ".tar.ga": TarFileList,
  ".tgz": TarFileList,
  ".zip": ZipFileList,
}

func ArchiveFileListMap(file string) ([]string, error) {
  if function, ok := FunctionForSuffix[Suffix(file)]; ok {
    return function(file)
  }
  return nil, errors.New("unrecognized archive")
}
  • 通过映射可以使事情变得条理清晰
  • 还可以动态地往映射里增加其他的项
  • 映射查询的速度不会随着项目的增加而降低
动态函数的创建
  • 例子:
var IsPalindrome func(string) bool
func init() {
  if len(os.Args) > 1 && (os.Args[1] == "-a" || os.Args[1] == "--ascii") {
    os.Args = append(os.Args[:1], os.Args[2:]...) // 去掉参数
    IsPalindrome = func(s string) bool { // 简单的 ASCII 版本
      if len(s) <= 1 {
        return true
      }
      if s[0] != s[len(s) - 1] {
        return false
      }
      return IsPalindrome(s[1 : len(s) - 1])
    }
  } else {
    IsPalindrome = func(s string) bool { // 简单的 ASCII 版本
    // 同前
    }
  }
}
  • 实际代码被执行完全取决于创建的是哪个版本的函数

推荐阅读更多精彩内容