基础语法

变量定义

1
2
3
4
5
6
7
8
var a int // 变量名在前 类型在后

var (
a int = 3
b int =4
) // 使用中括号避免多次使用var

a, b, c := 3, "abc", true // 使用冒号定义变量

变量类型

  • 变量类型

    • 变量类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      bool

      string

      int int8 int16 int32 int64
      uint uint8 uint16 uint32 uint64 uintptr

      byte // uint8 的别名

      rune // int32 的别名
      // 代表一个Unicode码

      float32 float64

      complex64 complex128
    • 原生支持复数 i

    • GO强制类型转换

      1
      2
      3
      4
      5
      6
      func triangle()  {
      var a, b int = 3, 4
      var c int
      c = int(math.Sqrt(float64(a*a + b*b)))
      fmt.Println(c)
      }

常量

  • const数值可作为各种类型使用 (没有规定类型)
  • 编译器可推算变量类型
  • iota 自增值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const (
a = iota
b
c
d
)
fmt.Println(a, b, c, d) // 0,1,2,3

// iota 可以作为一个表达式参与运算
const (
a = 1 << (10 * iota)
b
c
d
)
fmt.Println(a, b, c, d) // 1 1024 1048576 1073741824

条件语句

  • if语句

    • if 条件没有括号
    • if 条件里可以赋值
    • if 条件可以多条语句用’;’分隔开
    • if 条件里赋值的变量作用域就在这个if语句块里
      1
      2
      3
      4
      5
      6
         const filename = "abc.txt"
      if contents, err := ioutil.ReadFile(filename); err != nil {
      fmt.Println(err)
      } else {
      fmt.Printf("%s\n",contents)
      }
  • switch 语句

    • switch 会自动break, 除非使用fallthrough
    • switch 后面没有表达式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      func grade(score int) string  {
      g := ""
      switch {
      case score < 0 || score > 100:
      panic(fmt.Sprint(
      "wrong score: %d", score)) // 打印报错信息
      case score < 60:
      g = "F"
      }
      return g
      }

循环

  • for
    • for 的条件里不需要括号
    • for 的条件里可以省略初始条件、结束条件、递增表达式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
          func converToBin(n int) string  {
      result := ""

      for ; n > 0; n/=2 {
      lsb := n%2
      result = strconv.Itoa(lsb) + result
      }

      return result
      }

函数

  • func
    • 返回值类型写在最后面
    • 可返回多个值
    • 函数可作为参数
    • 没有默认参数,可选参数
    • 可变参数
      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
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      package main

      import (
      "fmt"
      "math"
      "reflect"
      "runtime"
      )

      func eval(a, b int, op string) (int, error) {
      switch op {
      case "+":
      return a + b, nil
      case "-":
      return a - b, nil
      case "*":
      return a * b, nil
      case "/":
      q, _ := div(a, b)
      return q, nil
      default:
      return 0, fmt.Errorf(
      "unsupported operation: %s", op)
      }
      }

      func div(a, b int) (q, r int) {
      return a / b, a % b
      }

      // 函数作为参数
      func apply(op func(int, int) int, a, b int) int {
      p := reflect.ValueOf(op).Pointer()
      opName := runtime.FuncForPC(p).Name()
      fmt.Printf("Calling function %s with args "+
      "(%d, %d)\n", opName, a, b)

      return op(a, b)
      }

      // 可变参数列表
      func sum(numbers ...int) int {
      s := 0
      for i := range numbers {
      s += numbers[i]
      }
      return s
      }

      // 交换两个传入的变量
      // 返回值并重新赋值(推荐)
      func swap(a, b int) (int, int) {
      return b, a
      }
      // 利用指针传值
      func swapT(a,b *int) {
      *b, *a = *a, *b
      }
      func main() {
      fmt.Println("Error handling")
      if result, err := eval(3, 4, "x"); err != nil {
      fmt.Println("Error:", err)
      } else {
      fmt.Println(result)
      }
      q, r := div(13, 3)
      fmt.Printf("13 div 3 is %d mod %d\n", q, r)

      fmt.Println("pow(3, 4) is:", apply(
      func(a int, b int) int {
      return int(math.Pow(
      float64(a), float64(b)))
      }, 3, 4))

      fmt.Println("1+2+...+5 =", sum(1, 2, 3, 4, 5))

      a, b := 3, 4
      a, b = swap(a, b)
      fmt.Println("a, b after swap is:", a, b)
      }

指针

  • 指针

    • 不能运算
    • 参数传递
      • 值传递(只是拷贝值不改变原始变量)
      GO语言参数传递只有值传递这一种方式
      • 引用传递
        &a是指向a的地址
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        void pass_by_val(int a) {
        a++
        }

        void pass_by_ref(int& a) {
        a++
        }

        int main () {
        int a = 3

        pass_by_val(a) // 3 值传递
        pass_by_ref(a) // 4 引用传递
        }
  • & 和 * 的区别

    • & 是取地址符号 , 即取得某个变量的地址 , 如 ; &a
    • 是指针运算符 , 可以表示一个变量是*指针类型** , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 .

      内建容器

      数组、切片、容器

      数组

    • 数量写在类型前
    • 数组是值类型
    • [10]int 和 [20]int 是不同的类型
    • go 语言中不直接使用数组而是使用切片
      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
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      package main

      import "fmt"

      func printArry(arr [5]int) {
      arr[0] = 100
      for i, v := range arr {
      fmt.Println(i, v)
      }
      }

      // 指针传递
      func printArrayPoint(arr *[5]int) {
      arr[0] = 100
      for i, v := range arr {
      fmt.Println(i, v)
      }
      }

      func main() {
      var arr1 [5]int
      arr2 := [3]int{1, 2, 4}
      arr3 := [...]int{2, 4, 5, 6, 7 } // 使用... 让编辑器计算个数
      var grid [4][5]int // 四行五列

      fmt.Println(arr1, arr2, arr3, grid)

      for i := 0; i < len(arr2); i ++ {
      fmt.Println(arr2[i])
      }

      // 使用range 关键字
      for i := range arr2 {
      fmt.Println(arr2[i])
      }

      for i, v := range arr2 {
      fmt.Println(i, v)
      }

      // 使用_占位符 省略变量
      for _, v := range arr2 {
      fmt.Println(v)
      }

      fmt.Println("printArry: arr3")
      printArry(arr3)
      fmt.Println(arr3) // arr3[0] 没有改变 [2 4 5 6 7]

      fmt.Println("printArry: arr3")
      printArrayPoint(&arr3)
      fmt.Println(arr3) // arr3[0] 改变 [100 4 5 6 7]
      }

切片

  • slice(切片)

    • slice 本身没有数据,是对底层array的一个view
    • 可以进行Reslice
    • slice扩展
      可以向前扩展,不可以向后扩展
      slice扩展
    • slice实现
      • ptr: slice取值的开端(ptr(s))
      • len: slice长度(len(s))
      • cap: 数组(cap(s))
      s[i]不可以超过len(s),向后扩展不可以超过底层狐族cap(s)
      slice实现
    • 向slice添加元素
      • 添加元素如果超过cap, 系统会重新分配更大的底层数组(go语言采用垃圾回收机制,原来的数组如果不再被使用则会被垃圾回收)
      • 由于值传递的关系,必须接收append的返回值(s = append(s, val))
        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
        package main

        import "fmt"

        func updateSlice(s []int) {
        s[0] = 100
        }

        func main() {
        arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
        s := arr[2:6]

        fmt.Println("arr[2:6]=", s) // [3 4 5 6]
        fmt.Println("arr[:6]=", arr[:6]) // [1 2 3 4 5 6]

        s1 := arr[2:]
        fmt.Println("arr[2:]=", arr[2:]) // [3 4 5 6 7 8 9]
        s2 := arr[:]
        fmt.Println("arr[:]=", arr[:]) // [1 2 3 4 5 6 7 8 9]

        updateSlice(s1)
        fmt.Println(s1) // [100 4 5 6 7 8 9]
        fmt.Println(arr)// 改变了arr [1 2 100 4 5 6 7 8 9]

        updateSlice(s2)
        fmt.Println(s2)//[100 2 100 4 5 6 7 8 9]

        fmt.Println("Reslice")
        s2 = s2[2:] // [100 4 5 6 7 8 9]
        fmt.Println(s2)
        s2 = s2[:5] // [100 4 5 6 7]
        fmt.Println(s2)

        s3 := append(s2, 10)
        s4 := append(s3, 11)
        s5 := append(s4, 12)
        fmt.Println("s3, s4, s5 =", s3, s4, s5) // s3, s4, s5 = [100 4 5 6 7 10] [100 4 5 6 7 10 11] [100 4 5 6 7 10 11 12]
        // s5 no longer view arr
        fmt.Println("arr=", arr) // arr= [100 2 100 4 5 6 7 10 11]
        }
  • 切片操作

    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
    43
    44
    45
    46
    47
    48
    49
    package main

    import "fmt"

    func printSlice(s []int) {
    fmt.Printf("%v, len=%d, cap=%d\n",
    s, len(s), cap(s))
    }

    func sliceOps() {
    fmt.Println("Creating slice")
    var s []int // Zero value for slice is nil

    for i := 0; i < 100; i++ {
    printSlice(s)
    s = append(s, 2*i+1)
    }
    fmt.Println(s)

    s1 := []int{2, 4, 6, 8}
    printSlice(s1)

    s2 := make([]int, 16)
    s3 := make([]int, 10, 32)
    printSlice(s2)
    printSlice(s3)

    fmt.Println("Copying slice")
    copy(s2, s1)
    printSlice(s2)

    fmt.Println("Deleting elements from slice")
    s2 = append(s2[:3], s2[4:]...)
    printSlice(s2)

    fmt.Println("Popping from front")
    front := s2[0]
    s2 = s2[1:]

    fmt.Println(front)
    printSlice(s2)

    fmt.Println("Popping from back")
    tail := s2[len(s2)-1]
    s2 = s2[:len(s2)-1]

    fmt.Println(tail)
    printSlice(s2)
    }

Map

Map的基础

  • Map的定义

    • map[k]v, map[k1]map[k2]v
    • make(map(k)v)
    • 获取元素m[k]
    • 当key 不存在时,获取Value类型的初始值
    • 用value, ok := m[key]来判断是否存在key
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
         m := map[string]string{
      "name": "ccmouse",
      "course": "golang",
      "site": "imooc",
      "quality": "notbad",
      }

      m2 := make(map[string]int) // m2 == empty map

      var m3 map[string]int // m3 == nil

      fmt.Println("m, m2, m3:")
      fmt.Println(m, m2, m3) // map[course:golang name:ccmouse quality:notbad site:imooc] map[] map[]
  • Map的遍历

    • 使用range 遍历
    • 不保证遍历顺序,如需顺序,需手动对key 排序
    • 用len获取元素个数
      1
      2
      3
      4
         fmt.Println("Traversing map m")
      for k, v := range m {
      fmt.Println(k, v)
      }
  • Map 操作

    • map使用hash表,必须可以比较相等
    • 除了slice、map、function的内建类型都可以作为key
    • Struct类型不包含上述字段,也可以作为key
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
         fmt.Println("Getting values")
      courseName := m["course"]
      fmt.Println(`m["course"] =`, courseName)
      if causeName, ok := m["cause"]; ok {
      fmt.Println(causeName)
      } else {
      fmt.Println("key 'cause' does not exist")
      }

      fmt.Println("Deleting values")
      name, ok := m["name"]
      fmt.Printf("m[%q] before delete: %q, %v\n",
      "name", name, ok)

      delete(m, "name")
      name, ok = m["name"]
      fmt.Printf("m[%q] after delete: %q, %v\n",
      "name", name, ok)

Map的例题

  • 寻找最长不含有重复字符的子串
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
// 不能处理中文 只能处理ASCII码 
func lengthOfNonReaptingSubStr(s string) int {
lastOccurred := make(map[byte]int)
start := 0
maxlength := 0

str := []byte(s)

fmt.Println(str)
for i, ch := range str {
lastI, ok := lastOccurred[ch]
if ok && lastI >= start {
start = lastI + 1
}

if i - start + 1 > maxlength {
maxlength = i - start + 1
}

lastOccurred[ch] = i
}
return maxlength
}

// 国际版 使用rune
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0

for i, ch := range []rune(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}

return maxLength
}

字符和字符串

  • 使用range 遍历 pos, rune(位置,值)对
  • 使用 utf8.RuneCountInString()获得字符数量
  • 使用len 获取字节长度
  • 使用[]byte获得字节
  • 字符串其他操作(strings包)
    • Fields、Split、Join
      Fields 以连续的空白字符为分隔符,将 s 切分成多个子串,结果中不包含空白字符本身
    • Contains、Index
    • ToLower、ToUpper
    • Trim、TrimRight、TrimLeft
      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
      package main

      import (
      "fmt"
      "unicode/utf8"
      )

      func main() {
      s := "Yes我爱慕课网!" // UTF-8
      fmt.Println(s)

      for _, b := range []byte(s) {
      fmt.Printf("%X ", b)
      }
      fmt.Println()

      for i, ch := range s { // ch is a rune
      fmt.Printf("(%d %X) ", i, ch)
      }
      fmt.Println()

      fmt.Println("Rune count:",
      utf8.RuneCountInString(s))

      bytes := []byte(s)
      fmt.Println(bytes)
      for len(bytes) > 0 {
      ch, size := utf8.DecodeRune(bytes)
      bytes = bytes[size:]
      fmt.Printf("%c ", ch)
      }
      fmt.Println()

      for i, ch := range []rune(s) {
      fmt.Printf("(%d %c) ", i, ch)
      }
      fmt.Println()
      }

面向对象

面向对象

  • go 仅支持封装,不支持继承和多态
  • go 语言没有class, 只有struct

结构

结构的创建

  • 使用自定义工厂函数
  • 注意返回了局部变量的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type treeNode struct {
value int
left, right *treeNode
}

// 自定义工厂函数
func createNode(val int) *treeNode {
return &treeNode{value: val}
}

func main() {
var root treeNode
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil }
root.right.left = new(treeNode)
root.left.right = createNode(2)
fmt.Println(root)
}

使用指针作为方法接收者

  • 只有使用指针才能改变结构内容

    1
    2
    3
    func (node *treeNode) setValue(value int)  {
    node.value = value
    }
  • nil指针也可以调用此方法

值接收者VS指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • 建议一致性:如果有指针接收者,最好都是指针接收者
  • 值接收者是Go 语言特有
  • 值/指针接收者均可接收值/指针

封装

  • 名字一般使用CamelCase(单词首字母大写)
  • 首字母大写: public (针对包而言)
  • 首字母小写: private (针对包而言)

  • 每个目录只有一个包
  • main包 包含可执行入口(main 函数)
  • 为结构定义的方法必须放在同一个包内,但是可以是不同的文件

如何扩展系统类型或者别人的类型

  • 定义别名
    例子文件夹queue
  • 使用组合
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 使用组合方式
    type myTreeNode struct {
    node *tree.TreeNode // 不一定是指针也可以是具体的类型
    }

    func (myNode *myTreeNode) postOrder() {
    if myNode == nil || myNode.node == nil {
    return
    }

    left := myTreeNode{myNode.node.Left}
    left.postOrder()
    right := myTreeNode{myNode.node.Right}
    right.postOrder()
    myNode.node.Print()
    }

    // 具体的调用方式
    myRoot := myTreeNode{&root}
    myRoot .postOrder()

GOPATH 环境变量

GOPATH

  • 默认在~/go(unix,Linx), %USERPROFILE%\go(windows)
    • 建议就算使用默认GOPATH也进行相对应的设置
  • 官方推荐:所有项目和第三方库都放在同一个GOPATH下
  • 也可以将每个项目放置在不同的GOPATH下,那么需要给GOPATH 环境变量设置多个值,Go语言编译的时候会找到不同GOPATH中自己所要依赖的包。
    • pwd:查看当前所在目录
    • echo $GOPATH:查看当前项目的GOPATH
    • cat ~/.bash_profile: 查看环境变量

go get 获取第三方库

  • go get 安装gopm
  • 使用gopm 获取无法下载的包

接口

duck typing

duck typing基本概念

  • “像鸭子走路,像鸭子叫,长得像鸭子,那么就是鸭子。”
  • 描述事物的外部行为而非内部结构
  • 严格说go属于结构化类型系统,类似duck typing(看法不同)

其他语言中的duck typing

其他语言中的duck typing

接口的定义

在上面链接的例子里:
使用者: download (使用者规定接口必须有什么方法)
实现者:fetcher (实现者不需要具体实现某个接口,只需要实现接口里面的方法即可)

  • 接口由使用者定义

    • 接口的实现是隐式的
    • 接口只需要实现里面的方法
  • 查看接口变量

    • interface{} : 表示任何类型
    • Type Assertion
    • Type Switch
  • 接口组合

  • 常用系统接口

    • Stringer
      相当于toString()
    • Reader
    • Writer
      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
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      // main文件
      package main

      import (
      "fmt"
      "lichuanyang65/learngo/retriever/mock"
      real2 "lichuanyang65/learngo/retriever/real"
      "time"
      )

      type Retriever interface {
      Get(url string) string
      }
      type Poster interface {
      Post(url string, form map[string]string) string
      }

      // 接口的组合
      type RetrieverPoster interface {
      Retriever
      Poster
      // 还可以定义其他的方法
      }

      const url = "http://www.baidu.com"
      func download (r Retriever) string {
      return r.Get(url)
      }
      func post (poster Poster) string {
      return poster.Post(url, map[string]string{
      "keyWord": "test",
      })
      }

      // 使用者组合,实现者不需要研究接口如何定义
      // 对使用者session 来说实现者RetrieverPoster里面的两个接口如何实现不重要,只需要实现Post 和Get 函数即可
      // 而实现者RetrieverPoster 并不需要声明它实现了哪些接口,只要它有相关接口的所定义的方法
      func session (s RetrieverPoster) string {
      s.Post(url, map[string]string{
      "keyWord": "test",
      "contents": "another fake test",
      })
      return s.Get(url)
      }

      func main() {
      var r Retriever
      fmt.Printf("%T %v\n", r)

      r = mock.Retriever{"this is a fake test"} // 真实的值 值拷贝
      // 如何区分不能类型的r
      inspect(r)

      r = &real2.Retriever{
      "Mozill/5.0",
      time.Minute,
      } // 指针*real.Retriever &{Mozill/5.0 1m0s}
      // 因此接口不会再次使用指针,接口内部可能存在已经使用指针的情况
      inspect(r)

      // Type assertion
      // 使用.(具体的类型) 可以取得interface里面的这个类型
      if mockRetriever, ok := r.(mock.Retriever); ok {
      fmt.Println(mockRetriever.Contents)
      } else {
      fmt.Println("not a mock rettriever")
      }

      realRetriever := r.(*real2.Retriever)
      fmt.Println(realRetriever.Timeout)

      // 接口组合
      // 同时实现了Get Post
      retrieve := mock.Retriever{url}
      s := RetrieverPoster(&retrieve)
      fmt.Println(session(s))
      //fmt.Println(download(r))
      }

      // 区分r内部的类型
      func inspect(r Retriever) {
      switch v := r.(type) {
      case mock.Retriever:
      fmt.Println("Contents:", v.Contents)
      fmt.Printf("%T %v\n", r, r)
      case *real2.Retriever:
      fmt.Println("UserAgent", v.UserAgent)
      fmt.Printf("%T %v\n", r, r)
      }
      }

      // mock文件
      // 这里有些不规范,在一个包里如果使用指针则应该都使用指针
      package mock

      // 这是一个假的 retriever 类似duck typing
      type Retriever struct {
      Contents string
      }

      // 模拟实现main 中的retriever 接口的mGet 方法
      func (r Retriever) Get(url string) string {
      return r.Contents
      }

      // 值接收者不会修改值
      func (r *Retriever) Post(url string, form map[string]string) string {
      r.Contents = form["contents"]
      return r.Contents
      }



      // real
      package real

      import (
      "net/http"
      "net/http/httputil"
      "time"
      )

      type Retriever struct {
      UserAgent string
      Timeout time.Duration
      }

      func (r Retriever) Get(url string) string {
      resp, err := http.Get(url)

      if err != nil {
      panic(err)
      }

      result, err := httputil.DumpResponse(resp, true)

      resp.Body.Close()

      if err != nil {
      panic(err)
      }

      return string(result)
      }

函数

闭包

!(闭包)[闭包. png]

函数式编程