day03 课件笔记


上次作业讲解

  • 切片元素的去重 ```go package main

import "fmt"

// - 切片元素的去重 // 使用map的key func main() { s1 := []string{"abc", "def", "abc", "okok", "okok"}

m := make(map[string]struct{})
for _, i := range s1 {
    m[i] = struct{}{}
}
s2 := make([]string, 0)
for k := range m {
    s2 = append(s2, k)
}
fmt.Println(s1)
fmt.Println(s2)

}

# 今天的内容大纲
- go中的锁
- map
- 管道
- 流程控制
- 函数
- 结构体
- 接口
- 面向对象


# 今天的内容重点
- map 带过期时间 、分片、sync.map
- chan 也比较重要,goroutine 一起理解
- 函数 太重要

# 下一节要讲什么内容
- panic和defer
- 结构体
    - 结构体定义
    - 结构体声明
    - 结构体初始化
    - new函数介绍
    - 属性访问与修改
    - 结构体命名嵌入
    - 结构体匿名嵌入
    - 结构体指针类型嵌入
    - 结构体可见性
    - 结构体浅拷贝和深拷贝
- 面向对象
- 接口 

# 今天的作业
- 类似改造f1 f2 写一个函数 返回一个 切片,在defer要去操作,考察return+命名返回+slice操作+defer
- 不定长参数函数 : 合并多个map,return新的map  
    - 因为没讲到结构体,可以看看下面的两个
    - jobManager 增量更新job
    - 结构体 有数组 切片 map ,写一个接受不定长参数的函数,合并所有字段,然后返回一个新的结构体



# go中的锁
## 互斥锁
- sync.mutex 
- 获取到互斥锁的任务,阻塞其他任务来获取锁
- 意味着同一时间只能有一个任务 才能持有互斥锁

```go
package main

import (
    "log"
    "sync"
    "time"
)

// HcMutex 它是一个互斥锁
var HcMutex sync.Mutex

func runMutex(id int) {
    log.Printf("[任务id :%d][尝试获取锁]", id)
    HcMutex.Lock()
    log.Printf("[任务id :%d][获取到了锁]", id)
    time.Sleep(20 * time.Second)
    HcMutex.Unlock()
    log.Printf("[任务id :%d][干完活了 释放锁]", id)
}

func runHcLock() {
    go runMutex(1)
    go runMutex(2)
    go runMutex(3)
}

func main() {
    runHcLock()

    time.Sleep(6 * time.Minute)
    /*
       2021/07/11 18:11:02 [任务id :3][尝试获取锁]
       2021/07/11 18:11:02 [任务id :3][获取到了锁]
       2021/07/11 18:11:02 [任务id :1][尝试获取锁]
       2021/07/11 18:11:02 [任务id :2][尝试获取锁]
       2021/07/11 18:11:22 [任务id :1][获取到了锁]
       2021/07/11 18:11:22 [任务id :3][干完活了 释放锁]
       2021/07/11 18:11:42 [任务id :1][干完活了 释放锁]
       2021/07/11 18:11:42 [任务id :2][获取到了锁]

    */
}

读写锁

  • sync.RWmutex
  1. 同时多个写锁任务,说明如果并非使用读写锁的写锁时,退化成了互斥锁

  2. 同时多个读锁任务,说明使用读写锁的读锁,可以同时施加多把读锁

  3. 先启动写锁任务,后并发5个读锁任务. 当有写锁存在时,读锁是施加不了的。写锁释放完,读锁可以施加多个

  4. 先并发5个读锁任务,后启动一个写锁任务. 当有读锁时,阻塞写锁。

  • 举例 ```go package main

import ( "log" "sync" "time" )

var rwMutex sync.RWMutex

func runReadLock(id int) { log.Printf("[读任务id :%d][进入读方法尝试获取读锁]", id) rwMutex.RLock() log.Printf("[读任务id :%d][获取到了读锁][开始干活 睡眠10秒]", id) time.Sleep(10 * time.Second) rwMutex.RUnlock() log.Printf("[读任务id :%d][完成读任 释放读锁]", id) }

func runWriteLock(id int) { log.Printf("[写任务id :%d][进入写方法尝试获取写锁]", id) rwMutex.Lock() log.Printf("[写任务id :%d][获取到了写锁][开始干活 睡眠10秒]", id) time.Sleep(10 * time.Second) rwMutex.Unlock() log.Printf("[写任务id :%d][完成写任务 释放写锁]", id) }

// 全是写任务 func allWriteWorks() { for i := 1; i < 6; i++ { go runWriteLock(i) } }

// 全是读任务 func allReadWorks() { for i := 1; i < 6; i++ { go runReadLock(i) } }

// 先启动写任务 func writeFirst() { go runWriteLock(1) time.Sleep(1 * time.Second) go runReadLock(1) go runReadLock(2) go runReadLock(3) go runReadLock(4) go runReadLock(5) }

// 先启动写任务 func readFirst() { go runReadLock(1) go runReadLock(2) go runReadLock(3) go runReadLock(4) go runReadLock(5)

time.Sleep(1 * time.Second) go runWriteLock(1) }

func main() { log.Printf("执行读写锁效果的函数") // 1. 同时多个写锁任务,说明如果并非使用读写锁的写锁时,退化成了互斥锁 //allWriteWorks()

// 2. 同时多个读锁任务,说明使用读写锁的读锁,可以同时施加多把读锁 //allReadWorks()

// 3. 先启动写锁任务,后并发5个读锁任务. 当有写锁存在时,读锁是施加不了的。写锁释放完,读锁可以施加多个 //writeFirst()

// 4. 先并发5个读锁任务,后启动一个写锁任务. 当有读锁时,阻塞写锁。 readFirst()

time.Sleep(1 * time.Hour) }

# map使用


## 声明和初始化 
### 使用var只声明
- `// 只声明
   var gMap map[string]string`
### 使用var声明同时初始化
- `// 声明初始化
   var hMap = map[string]string{"a": "b"}`

### 使用make 初始化


## 增删改查
### 增加和修改


### 删除

### 读取数据
- 两种手段 第一种 单变量 lang:=m["lang"],没有key就附一个value类型的0值
- 两种手段 第二种 双变量 app2,exists := m["app2"],可以根据exists判断 true代表有key


### 增删改查举例
- 举例
```go
package main

import "fmt"

// 只声明
var m1 map[string]string

// 声明又初始化
var m2 = map[string]string{"a": "b"}

func main() {
   m := make(map[string]string)
   // 增加
   m["app"] = "taobao"
   m["lang"] = "golang"
   // 删除
   delete(m, "app")
   fmt.Println(m)
   // 改
   m["lang"] = "python"
   // 查
   // 单变量形式
   lang := m["lang"]
   fmt.Println(lang)
   // 双变量形式
   lang1, exists := m["lang"]
   if exists {
      fmt.Printf("[lang存在 值:%v]\n", lang1)
   } else {
      fmt.Println("lang不存在\n")
      m["lang"] = "java"
   }

}

遍历map和按key的顺序遍历

  • 举例 ```go package main

import "fmt"

// 只声明 var m1 map[string]string

// 声明又初始化 var m2 = map[string]string{"a": "b"}

func main() { m := make(map[string]int) keys := make([]string, 0) for i := 0; i < 20; i++ { key := fmt.Sprintf("key_%d", i) keys = append(keys, key) m[key] = i } fmt.Println(m) // range 遍历 keys //for k := range m { // fmt.Printf("[key=%s]\n", k) //}

fmt.Println("无序遍历")
// range 遍历
for k, v := range m {
    fmt.Printf("[%s=%d]\n", k, v)
}
// 有序key遍历
fmt.Println("有序遍历")
for _, k := range keys {
    fmt.Printf("[%s=%d]\n", k, m[k])
}

}

## key的类型:float64可以作为key吗
- bool、int、string
- 特征是 支持 == 和!=比较
- float型可以做为key的,写入map时会做math.Float64bits()的转换,认为2.4=2.40000xxxx1 ,看起来是同一个key
```go
package main

import "fmt"

func main() {
    m := make(map[float64]int)
    m[2.4] = 2
    fmt.Printf("k: %v, v: %d\n", 2.4000000000000000000000001, m[2.4000000000000000000000001])
    fmt.Println(m[2.4000000000000000000000001] == m[2.4])
}

value的类型:任意类型

  • map的value是个map,每一层map都要make
package main

func main() {
    var doubleM map[string]map[string]string
    // panic: assignment to entry in nil map
    //doubleM = make(map[string]map[string]string)
    v1 := make(map[string]string)
    v1["k1"] = "v1"
    doubleM["m1"] = v1

}

go原生的map线程不安全

fatal error :concurrent map read and map write

  • 举例 ```go package main

import "time"

func main() {

c := make(map[int]int)
// 匿名goroutine 循环写map
go func() {
    for i := 0; i < 10000; i++ {
        c[i] = i
    }
}()

// 匿名goroutine 循环读map
go func() {
    for i := 0; i < 10000; i++ {
        _ = c[i]
    }
}()

time.Sleep(30 * time.Minute)

}

### fatal error: concurrent map writes
- 举例
```go
package main

import "time"

func main() {

    c := make(map[int]int)
    // 匿名goroutine 循环写map
    go func() {
        for i := 0; i < 10000; i++ {
            c[i] = i
        }
    }()

    // 匿名goroutine 循环写map
    go func() {
        for i := 0; i < 10000; i++ {
            c[i] = i
        }
    }()
    //
    //// 匿名goroutine 循环读map
    //go func() {
    //  for i := 0; i < 10000; i++ {
    //      _ = c[i]
    //  }
    //}()

    time.Sleep(30 * time.Minute)
}

上述问题原因

解决方法之一 加锁

  • 使用读写锁 ```go package main

import ( "fmt" "sync" "time" )

// 为了解决map线程不安全 ,我们自己加锁

type concurrentMap struct { mp map[int]int sync.RWMutex }

// 通过set 方法做原有map的赋值 m[key] =v func (c *concurrentMap) Set(key, value int) { // 加写锁 c.Lock() c.mp[key] = value c.Unlock()

}

// 通过get 方法做原有map的读取值操作 v:= m[key] func (c *concurrentMap) Get(key int) int { //先获取读锁 c.RLock() res := c.mp[key] c.RUnlock() return res }

func main() { c := concurrentMap{ mp: make(map[int]int), } // 一个线程循环写map go func() { for i := 0; i < 10000; i++ { c.Set(i, i) } }() // 一个线程循环读map go func() { for i := 0; i < 10000; i++ { res := c.Get(i) fmt.Printf("[cmap.get][%d=%d]\n", i, res) } }() time.Sleep(1 * time.Hour)

}

### 解决方法之二 使用sync.map

- go 1.9引入的内置方法,并发线程安全的map
- sync.Map 将key和value 按照interface{}存储
- 查询出来后要类型断言 x.(int) x.(string)
- 遍历使用Range() 方法,需要传入一个匿名函数作为参数,匿名函数的参数为k,v interface{},每次调用匿名函数将结果返回。

- 举例

```go
package main

import (
    "fmt"
    "log"
    "strings"
    "sync"
)

func main() {

    m := sync.Map{}
    // 新增
    for i := 0; i < 10; i++ {
        key := fmt.Sprintf("key_%d", i)
        m.Store(key, i)
    }
    // 删除
    m.Delete("key_8")

    // 改m.Store
    m.Store("key_9", 999)

    // 查询
    res, loaded := m.Load("key_09")
    if loaded {
        //  类型断言 res.(int)
        log.Printf("[key_09存在 :%v 数字类型:%d]", res, res.(int))
    }

    // 遍历 return false 停止
    m.Range(func(key, value interface{}) bool {
        k := key.(string)
        v := value.(int)
        if strings.HasSuffix(k, "3") {
            log.Printf("不想要3")
            //return true
            return false
        } else {
            log.Printf("[sync.map.Range][遍历][key:=%s][v:=%d]", k, v)

            return true
        }

    })
    // LoadAndDelete 先获取值再删掉
    s1, loaded := m.LoadAndDelete("key_7")
    log.Printf("key_7 LoadAndDelete :%v", s1)
    s2, loaded := m.Load("key_7")
    log.Printf("key_7 LoadAndDelete:%v", s2)

    actual, loaded := m.LoadOrStore("key_8", 158)
    if loaded {
        log.Printf("key_8原来的值是:%v", actual)
    } else {
        log.Printf("key_8原来没有,实际是:%v", actual)
    }

    actual, loaded = m.LoadOrStore("key_1", 158)
    if loaded {
        log.Printf("key_1原来的值是:%v", actual)
    } else {
        log.Printf("key_1原来没有,实际是:%v", actual)
    }
}

sync.map 性能对比

  • https://studygolang.com/articles/27515

  • 性能对比结论 shell script 只读场景:sync.map > rwmutex >> mutex 读写场景(边读边写):rwmutex > mutex >> sync.map 读写场景(读80% 写20%):sync.map > rwmutex > mutex 读写场景(读98% 写2%):sync.map > rwmutex >> mutex 只写场景:sync.map >> mutex > rwmutex

  • sync.Map使用场景的建议

    • 读多:给定的key-v只写一次,但是读了很多次,只增长的缓存场景
    • key不相交: 覆盖更新的场景比少
  • 结构体复杂的case多不用sync.Map

上述两种手段有什么问题

  • 没有精细化锁控制 没有分片
  • 加了大锁

分片锁 并发map github.com/orcaman/concurrent-map

  • 基础用法 ```go package main

import ( "fmt" "github.com/orcaman/concurrent-map" "log" "time" )

func main() { // Create a new map. m := cmap.New()

// 循环写map
go func() {

    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("key_%d", i)
        m.Set(key, i)
    }

}()
// 循环读map
go func() {

    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("key_%d", i)
        v, exists := m.Get(key)
        if exists {
            log.Printf("[%s=%v]", key, v)
        }
    }

}()
// 循环写map
go func() {

    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("key_%d", i)
        m.Set(key, i)
    }

}()
// 循环写map
go func() {

    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("key_%d", i)
        m.Set(key, i)
    }

}()
// 循环写map
go func() {

    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("key_%d", i)
        m.Set(key, i)
    }

}()

time.Sleep(1 * time.Hour)

}

### 带过期时间的map
- 为什么要有过期时间
- map做缓存用的 垃圾堆积k1  k2 
- 希望缓存存活时间 5分钟,
- 将加锁的时间控制在最低,
- 耗时的操作在加锁外侧做

```go
package main

import (
    "fmt"
    "log"
    "sync"
    "time"
)

//带过期时间的map 定时清理

type Cache struct {
    sync.RWMutex
    mp map[string]*item
}

type item struct {
    value int   //值
    ts    int64 // 时间戳,item 被创建出来的时间
}

func (c *Cache) Get(key string) *item {
    c.RLock()
    defer c.RUnlock()
    return c.mp[key]
}

func (c *Cache) CacheNum() int {
    c.RLock()

    keys := make([]string, 0)
    //i := 0
    for k, _ := range c.mp {
        //fmt.Println(k)
        keys = append(keys, k)
        //i++

    }
    c.RUnlock()
    return len(keys)
}

func (c *Cache) Set(key string, value *item) {
    c.Lock()
    defer c.Unlock()
    c.mp[key] = value
}

func (c *Cache) Clean(timeDelta int64) {
    // 每5秒执行一此清理
    for {
        now := time.Now().Unix()
        // 待删除的key的切片
        toDelKeys := make([]string, 0)

        // 先加读锁,把所有待删除的拿到
        c.RLock()
        for k, v := range c.mp {
            // 时间比较
            if now-v.ts > timeDelta {
                // 认为这个k,v过期了,
                // 不直接删除,为了降低加锁时间,加入待删除的切片
                toDelKeys = append(toDelKeys, k)
            }
        }
        c.RUnlock()

        // 加写锁 删除,降低加写锁的时间
        c.Lock()
        for _, k := range toDelKeys {
            log.Printf("[删除过期数据][key:%s]", k)
            delete(c.mp, k)
        }
        c.Unlock()
        //  写锁释放
        time.Sleep(2 * time.Second)
    }
}

func main() {
    c := Cache{
        mp: make(map[string]*item),
    }
    // 让清理的任务异步执行
    // 每2秒运行一次,检查时间差大于30秒item 就删除
    go c.Clean(30)

    // 从mysql中读取到了数据,塞入缓存
    for i := 0; i < 10; i++ {
        key := fmt.Sprintf("key_%d", i)
        ts := time.Now().Unix()
        im := &item{
            value: i,
            ts:    ts,
        }
        // 设置缓存
        log.Printf("[设置缓存][item][key:%s][v:%v]", key, im)
        c.Set(key, im)
    }
    log.Printf("缓存中的数据量:%d", c.CacheNum())
    time.Sleep(33 * time.Second)

    log.Printf("缓存中的数据量:%d", c.CacheNum())

    // 更新缓存
    for i := 0; i < 5; i++ {
        key := fmt.Sprintf("key_%d", i)
        ts := time.Now().Unix()
        im := &item{
            value: i,
            ts:    ts,
        }
        // 设置缓存
        log.Printf("[更新缓存][item][key:%s][v:%v]", key, im)
        c.Set(key, im)
    }
    log.Printf("缓存中的数据量:%d", c.CacheNum())
    select {}
    /*2021/07/18 11:48:36 [设置缓存][item][key:key_0][v:&{0 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_1][v:&{1 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_2][v:&{2 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_3][v:&{3 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_4][v:&{4 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_5][v:&{5 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_6][v:&{6 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_7][v:&{7 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_8][v:&{8 1626580116}]
    2021/07/18 11:48:36 [设置缓存][item][key:key_9][v:&{9 1626580116}]
    2021/07/18 11:48:36 缓存中的数据量:10
    2021/07/18 11:49:08 [删除过期数据][key:key_2]
    2021/07/18 11:49:08 [删除过期数据][key:key_4]
    2021/07/18 11:49:08 [删除过期数据][key:key_5]
    2021/07/18 11:49:08 [删除过期数据][key:key_8]
    2021/07/18 11:49:08 [删除过期数据][key:key_9]
    2021/07/18 11:49:08 [删除过期数据][key:key_0]
    2021/07/18 11:49:08 [删除过期数据][key:key_1]
    2021/07/18 11:49:08 [删除过期数据][key:key_3]
    2021/07/18 11:49:08 [删除过期数据][key:key_6]
    2021/07/18 11:49:08 [删除过期数据][key:key_7]
    2021/07/18 11:49:09 缓存中的数据量:0
    2021/07/18 11:49:09 [更新缓存][item][key:key_0][v:&{0 1626580149}]
    2021/07/18 11:49:09 [更新缓存][item][key:key_1][v:&{1 1626580149}]
    2021/07/18 11:49:09 [更新缓存][item][key:key_2][v:&{2 1626580149}]
    2021/07/18 11:49:09 [更新缓存][item][key:key_3][v:&{3 1626580149}]
    2021/07/18 11:49:09 [更新缓存][item][key:key_4][v:&{4 1626580149}]
    2021/07/18 11:49:09 缓存中的数据量:5
    2021/07/18 11:49:40 [删除过期数据][key:key_0]
    2021/07/18 11:49:40 [删除过期数据][key:key_2]
    2021/07/18 11:49:40 [删除过期数据][key:key_3]
    2021/07/18 11:49:40 [删除过期数据][key:key_1]
    2021/07/18 11:49:40 [删除过期数据][key:key_4]

    */

}

带过期时间的缓存 github.com/patrickmn/go-cache

简单应用上手 ```go package main

import ( "fmt" "github.com/patrickmn/go-cache" "time" )

func main() { // Create a cache with a default expiration time of 5 minutes, and which // purges expired items every 10 minutes c := cache.New(30time.Second, 5time.Second)

// eSet the value of the key "foo" to "bar", with the default xpiration time
c.Set("k1", "v1", 31*time.Second)
res, ok := c.Get("k1")
fmt.Println(res, ok)
time.Sleep(time.Second * 32)
res, ok = c.Get("k1")
fmt.Println(res, ok)

}

> 生产上的 web缓存应用
- 查询触发性质的
```go
package main

import (
    "fmt"
    "github.com/patrickmn/go-cache"
    "log"
    "time"
)

// 生产上的 web缓存应用
// 维护用户信息的模块
// 在mysql中有一张 user表
// 正常情况是用orm gorm xorm 去db中查询
// 查询qps很高,为了性能会加缓存
//(更新不会太频繁) ,说明在一定时间内,获取到旧数据也能容忍

type user struct {
    Name  string
    Email string
    Phone int64
}

var (
    DefaultInterval = time.Minute * 1
    UserCache       = cache.New(DefaultInterval, DefaultInterval)
)

// 最外层调用函数
// 优先去本地缓存中查,有就返回
// 没有再去远端查询 ,远端用http表示
func GetUser(name string) user {
    // 消耗 0.1cpu 0.1M内存 0.1秒返回
    res, found := UserCache.Get(name)
    if found {
        //if res, found := UserCache.Get(name); found {
        log.Printf("[本地缓存中找到了对应的用户][name:%v][value:%v]", name, res.(user))
        return res.(user)
        // 消耗 1cpu 10M内存 3秒返回
    } else {
        res := HttpGetUser(name)
        log.Printf("[本地缓存中没找到对应的用户,去远端查询获取到了,塞入缓存中][name:%v][value:%v]", name, res)
        // 本地没有,但是从远端拿到了最新的数据
        // 更新本地缓存 ,我种树,其他人乘凉
        UserCache.Set(name, res, DefaultInterval)
        return res
    }
}
func HttpGetUser(name string) user {
    // mock 模拟我去接口查
    u := user{
        Name:  name,
        Email: "qq.com",
        Phone: time.Now().Unix(),
    }
    return u
}

// 查询方法

func queryUser() {
    for i := 0; i < 10; i++ {
        userName := fmt.Sprintf("user_name_%d", i)
        GetUser(userName)
    }
}

func main() {
    log.Printf("第1次query_user")
    queryUser()
    log.Printf("第2次query_user")
    queryUser()
    queryUser()
    queryUser()
    queryUser()
    queryUser()
    time.Sleep(61 * time.Second)
    log.Printf("第3次query_user")
    queryUser()
}

map的实际应用

image image image

map的原理

image image image

map底层原理文章推荐

channel

  • Channel是Go中的一个核心类型
  • 你可以把它看成一个管道

像 map 和 slice 数据类型一样, channel必须先创建再使用:

ch := make(chan int)

箭头的指向就是数据的流向

ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v

channel可进行3种操作

  • 关闭

channel存在3种状态:

  • nil ,未初始化的状态: 只用var 只声明了,或者手动赋值为nil
  • active ,正常的channel 。可以读或写
  • closed ,已关闭的。它的值不是nil

同步模式

  • channel默认为同步模式,即不创建缓冲区,发送和接收需要一一配对,不然发送方会被一直阻塞
package main

import (
    "log"
    "time"
)

func main() {

    // 初始化一个类型为int的chan
    data := make(chan int)
    // 读取数据的任务
    go func() {

        for {
            r := <-data
            log.Printf("[接收到了数据,开始处理]:%v", r)
        }

    }()

    // 写入数据
    data <- 1
    time.Sleep(2 * time.Second)
    data <- 2
    time.Sleep(2 * time.Second)
    data <- 3
    time.Sleep(2 * time.Second)
    close(data)
}

使用range遍历

  • 使用for-range读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。
  • 如果不加最后这个sleep , chan 关闭了不会打印 ```go package main

import ( "log" "time" )

func main() {

// 初始化一个类型为int的chan
data := make(chan int)
// 读取数据的任务
go func() {
    for r := range data {
        log.Printf("[接收到了数据,开始处理]:%v", r)
    }
    log.Printf("chan 关闭了")
}()

// 写入数据
data <- 1
time.Sleep(2 * time.Second)
data <- 2
time.Sleep(2 * time.Second)
data <- 3
time.Sleep(2 * time.Second)
close(data)
time.Sleep(1 * time.Second)
// 现象是  chan 关闭了没打印
// 如果加上 time.Sleep(1 * time.Second) 就会打印

}

> 使用 if双返回值判断 chan是否被关闭
- ok=false通道关闭,无数据读到。

```go
package main

import (
    "log"
    "time"
)

func main() {

    // 初始化一个类型为int的chan
    data := make(chan int)
    // 读取数据的任务
    go func() {
        for {
            if r, ok := <-data; ok {
                log.Printf("[接收到了数据,开始处理]:%v", r)
            } else {
                log.Printf("chan 关闭了")
                break
            }

        }
    }()

    // 写入数据
    data <- 1
    time.Sleep(2 * time.Second)
    data <- 2
    time.Sleep(2 * time.Second)
    data <- 3
    time.Sleep(2 * time.Second)
    close(data)
    time.Sleep(1 * time.Second)
    // 现象是  chan 关闭了没打印
    // 如果加上 time.Sleep(1 * time.Second) 就会打印

}

异步模式

  • 异步模式channel有缓冲区
  • 如果缓冲区已满,发送的主进程或者协程会被阻塞
  • 如果未满不会被阻塞
  • 如果为空,接收的协程会被阻塞
  • 基于这种性质往往需要有个同步channel去控制主进程是否退出,否则有可能协程还未处理完所有的信息,主进程已经退出
  • 代码 ```go package main

import ( "log" "time" )

func main() {

// 初始化一个类型为int的chan ,缓冲区为3
data := make(chan int, 3)
quit := make(chan bool) // 达成和最后的time.sleep一样的效果 阻主线程,防止异步任务中有未处理完的就推出
// 读取数据的任务
go func() {
    for d := range data {
        time.Sleep(2 * time.Second)
        log.Printf("[接收到了数据,开始处理]:%v", d)

    }
    log.Printf("data chan关闭了,但是我还有清理工作,等我5秒钟")
    time.Sleep(5 * time.Second)
    // 本任务处理完了,告诉主线程可以退出了
    quit <- true
}()

// 写入数据
data <- 1
time.Sleep(2 * time.Second)
data <- 2
time.Sleep(2 * time.Second)
data <- 3
time.Sleep(2 * time.Second)
data <- 4
log.Printf("发送4")
data <- 5
log.Printf("发送5")
data <- 6
log.Printf("发送6")
data <- 7
log.Printf("发送7")
data <- 8
log.Printf("发送8")
data <- 9
log.Printf("发送9")
close(data)
<-quit
log.Printf("真正退出了")

}

> 从一个close的chan 中读取到的是0值
-  如果改成 for { <-data} 这样去读取
- 在chan被关闭之后就是读取到的就是0值
```go
package main

import (
    "log"
    "time"
)

func main() {

    // 初始化一个类型为int的chan ,缓冲区为3
    data := make(chan int, 3)
    quit := make(chan bool) // 达成和最后的time.sleep一样的效果 阻主线程,防止异步任务中有未处理完的就推出
    // 读取数据的任务
    go func() {
        for d := range data {
            time.Sleep(2 * time.Second)
            log.Printf("[接收到了数据,开始处理]:%v", d)

        }
        log.Printf("data chan关闭了,但是我还有清理工作,等我5秒钟")
        log.Printf("data chan关闭了,我再读取几个值看看:%v", <-data)
        log.Printf("data chan关闭了,我再读取几个值看看:%v", <-data)
        log.Printf("data chan关闭了,我再读取几个值看看:%v", <-data)

        time.Sleep(5 * time.Second)
        // 本任务处理完了,告诉主线程可以退出了
        quit <- true
    }()

    // 写入数据
    data <- 1
    time.Sleep(2 * time.Second)
    data <- 2
    time.Sleep(2 * time.Second)
    data <- 3
    time.Sleep(2 * time.Second)
    data <- 4
    log.Printf("发送4")
    data <- 5
    log.Printf("发送5")
    data <- 6
    log.Printf("发送6")
    data <- 7
    log.Printf("发送7")
    data <- 8
    log.Printf("发送8")
    data <- 9
    log.Printf("发送9")
    close(data)
    <-quit
    log.Printf("真正退出了")
}

几种会导致panic的 chan操作

01 close nil 的chan

02 向已关闭的chan 再次close

03 向已关闭的chan 再次写入值 ```go package main

func main() { // panic: close of nil channel //var c1 chan int //close(c1)

// panic: close of closed channel
//c2 := make(chan int)
//close(c2)
//close(c2)

// panic: send on closed channel
c3 := make(chan int)
close(c3)
c3 <- 1

}

## select 和close 
- close(ch) 通知所有下游协程 :watch 这个ch的worker
- 使用select处理多个channel
- select可以同时监控多个通道的情况,只处理未阻塞的case。当通道为nil时,对应的case永远为阻塞,无论读写
```go
package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

var quitC = make(chan struct{})

func signalWork() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
    // 当c中读取到值的时候,说明有人发了信号
    sig := <-c

    // 通知所有读取 quitC的任务
    close(quitC)
    time.Sleep(2 * time.Second)
    log.Printf("接收到了停止的信号 信号是:%v,pid:=%d,要退出了", sig, os.Getpid())
}

func worker01() {
    ticker := time.NewTicker(time.Second * 5)
    for {
        select {
        case <-ticker.C:
            log.Printf("[我是woker01][5秒周期到了,干活]")
        case <-quitC:
            log.Printf("[我是woker01][接受到主进程退出的信号]。进行清理操作")
            return
        }

    }

}

func worker02() {
    ticker := time.NewTicker(time.Second * 5)
    for {
        select {
        case <-ticker.C:
            log.Printf("[我是woker02][5秒周期到了,干活]")
        case <-quitC:
            log.Printf("[我是woker02][接受到主进程退出的信号]。进行清理操作")
            return
        }

    }

}
func worker03() {
    ticker := time.NewTicker(time.Second * 5)
    for {
        select {
        case <-ticker.C:
            log.Printf("[我是woker03][5秒周期到了,干活]")
        case <-quitC:
            log.Printf("[我是woker03][接受到主进程退出的信号]。进行清理操作")
            return
        }

    }

}

func main() {
    go worker01()
    go worker02()
    go worker03()
    signalWork()
}

什么是流程控制

  • 控制逻辑走向和执行次序的重要部分

go语言流程控制

  • Go中流程控制分三大类:条件判断,循环控制和无条件跳转。

01 条件判断

  • if 多条件 ```go package main

import "fmt"

func main() {

x := 11
y := "ok"
if x == 10 && y == "ok" {
    fmt.Println("两个都满足")
} else if x > 10 || y == "ok" {
    fmt.Println("任意一个")
}

}

- 条件判断语句里面允许声明一个变量
- 这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了
```go
package main

import (
    "fmt"
    "log"
)

func main() {

    m := map[string]string{
        "region": "bj",
        "idc":    "世纪互联",
    }

    if idc := m["idc"]; idc == "世纪互联" {
        log.Printf("机房:%v", idc)
    }
    idc1 := m["idc"]
    if idc1 == "世纪互联" {
        log.Printf("机房:%v", idc1)
    }

    fmt.Println(idc)
    fmt.Println(idc1)
}

switch条件判断

  • 基础使用
package main

import (
    "fmt"
)

func jj(s string) {
    switch s {
    case "go":
        fmt.Println("go")
    case "py":
        fmt.Println("py")
    case "java":
        fmt.Println("py")
    default:
        fmt.Println("unknow")
    }
}

func main() {

    jj("py")
    jj("go")
    jj("go1")
}

不允许两个default

  • // Multiple defaults in switch

case后面可以多个值

不允许case 两个常量相同 duplicate case "java" in switch

fallthrough 继续判断 ```go package main

import ( "fmt" )

func jj(s string) { java1 := "java" java2 := "java" switch s { case "go": fmt.Println("go") // case接多个 值 case "py", "python": fmt.Println("py")

case java1:

    fmt.Println("java")
    // 继续判断
    fallthrough
case java2:

    fmt.Println("java")
    // 继续判断
    //fallthrough
default:
    fmt.Println("unknow")
}

}

func main() {

jj("py")
jj("python")
jj("java")

}

## 02 循环控制
### 单个逻辑表达式
> 死循环
```go
package main

import (
    "log"
    "time"
)

func main() {
    for {
        log.Printf("我干活呢")
        time.Sleep(3 * time.Second)
    }
}

单条件判断 ```go package main

import ( "log" )

func main() { var a, b int b = 10 for a < b { log.Printf("我干活呢", a, b) a++ //time.Sleep(3 * time.Second) } }

### 双分号
- for语句被两个分号分割为3个表达式
- 第一个表示为初始化(只会在第一次条件表达式之计算一次)
- 第二个表达式为条件判断表达式
- 第三个表达式一般为自增或自减,但这个表达式可以任何符合语法的表达式
- 而且这三个表达式, 只有第二个表达式是必须有的,其他表达式可以为空
```go
package main

import "log"

func main() {
    for i := 0; i < 5; i++ {
        log.Printf("第1种 三段全的")
    }
    for i := 0; i < 5; {
        log.Printf("第2种 自增写在里面")
        i++
    }
    var i int
    for ; i < 5; {
        log.Printf("第3种 自增写在里面 ,初始化写在上面")
        i++
    }
}

for和range结合的语句

  • 用range来迭代数据是最常用的一种for语句
  • range右边的表达式叫范围表达式
  • 范围表达式可以是数组,数组指针,slice,字符串,map和channel ```go package main

import "log"

func main() { s1 := []int{10, 20, 30, 40, 50} m1 := map[string]string{"k1": "v1", "k2": "v2", "k3": "v3"}

for index := range s1 {
    log.Printf("[切片][range 遍历单变量 只有索引][index:%v]", index)
}
for index, value := range s1 {
    log.Printf("[切片][range 遍历双变量 索引和值][index:%v][value:%v]", index, value)
}

for key := range m1 {
    log.Printf("[map][range 遍历单变量 只有key][key:%v]", key)
}
for key, value := range m1 {
    log.Printf("[map][range 遍历双变量 key,value][key:%v][value:%v]", key, value)
}

}

> for range 给指针变量赋值时的问题
- 现象 给指针切片元素赋值的时候,值都是最后一个
```go
package main

import "log"

func main() {
    a1 := make([]*int, 3)
    a2 := make([]int, 3)

    for k, v := range []int{1, 2, 3} {
        log.Printf("[v的值:%v][v的地址:%p]", v, &v)
        a1[k] = &v
        a2[k] = v
    }

    for i := range a1 {
        log.Printf("[指针切片的值为:%v]", *a1[i])
    }
    for i := range a2 {
        log.Printf("[普通值类型切片的值为:%v]", a2[i])
    }
    /*
       2021/07/18 16:18:36 [v的值:1][v的地址:0xc0000ac058]
       2021/07/18 16:18:36 [v的值:2][v的地址:0xc0000ac058]
       2021/07/18 16:18:36 [v的值:3][v的地址:0xc0000ac058]
       2021/07/18 16:18:36 [指针切片的值为:3]
       2021/07/18 16:18:36 [指针切片的值为:3]
       2021/07/18 16:18:36 [指针切片的值为:3]
       2021/07/18 16:18:36 [普通值类型切片的值为:1]
       2021/07/18 16:18:36 [普通值类型切片的值为:2]
       2021/07/18 16:18:36 [普通值类型切片的值为:3]

    */
}
  • 问题原因

    //实例代码
    array :=[2]int{1,2}
    for k,v :=range array{
     f(k,v)
    }
    //会被编译成
    len_temp := len(array)
    range_temp :=array
    for index_temp =0 ;index_temp<len_temp;index_temp++{ 
      value_temp= range_temp[index_temp]
      k = index_temp
       v = value_temp
       f(k,v)
    }
    
  • 两个临时变量index_temp,value_temp, 在整个遍历中一直复用这两个变量

  • 所以&v得到的地址一直都是相同的
  • 解决方法 v:=v

continue和break ```go package main

import ( "fmt" "log" "strings" )

func main() {

m1 := make(map[string]string)
for i := 0; i < 30; i++ {
    key := fmt.Sprintf("%d_key", i)
    value := fmt.Sprintf("%d_value", i)
    m1[key] = value
}
for k, v := range m1 {
    if strings.HasPrefix(k, "1") {
        log.Printf("[遇到1就continue]")
        continue
    }
    if k == "23_key" {
        log.Printf("[遇到23就break]")
        break
    }
    log.Printf("[正常处理数据][%v=%v]", k, v)
}

}

## 03 无条件跳转
- goto代表无条件跳转
- 下面的例子会一直打印
```go
package main

import "log"

func main() {
    i := 0
sum:
    log.Printf("[i=%d]", i)
    i++
    goto sum
}
  • 结合if可以控制 ```go package main

import "log"

func main() { i := 0 sum: { log.Printf("[i=%d]", i) i++ } if i <= 100 { goto sum }

}

- goto 副作用 在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。


# 函数定义
```go
func function_name( [parameter list] ) [return_types] {
   函数体
}
  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

简单使用

package main

import "fmt"

func max(n1, n2 int) int {
    if n1 > n2 {
        return n1
    }
    return n2

}

func main() {
    fmt.Println(max(1, 10))
    fmt.Println(max(-1, -2))

}

Go语言支持对返回值进行命名

  • 命名的返回值变量的默认值为类型的0值
    • 相当于var xxx
    • map必须使用前make
package main

import "fmt"

func f1() (names []string, m map[string]int, num int) {
   m = make(map[string]int)
   m["k1"] = 2

   return
}

func main() {
   a, b, c := f1()
   fmt.Println(a, b, c)
}

不定长参数

  • 如果函数的最后一个参数是采用 ...type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。
  • 函数内部的参数是数组
  • 外部传参为数组时 ,可以使用 arr... ```go package main

import "log"

// 变长参数 返回最小值 func min(a ...int) int { if len(a) == 0 { return 0 } min := a[0] for _, v := range a { if v < min { min = v } } return min }

func main() { x1 := min(1, 7, 8, 3, 23, 9) log.Printf("[直接传多个参数]:%d", x1) s1 := []int{2, 3, 54, 10, 32, 5, 7, 34} x2 := min(s1...) log.Printf("[数组传参传]:%d", x2) }

## 按值传递 or 按引用传递
- Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量
- 如果希望值传递类型在函数中也可以修改变量,那么应该传递指针
- 引用类似的数据都是引用传递的,slice map chan interface 



> 值类型
```go
package main

import (
    "log"
    "time"
)

// 值类型 值传递
func add1(num int) {
    log.Printf("[值传递][传入的参数值为:%d]", num)
    num++
    log.Printf("[值传递][add1计算后的值为:%d]", num)
}

// 值类型 引用传递
func add2(num *int) {
    log.Printf("[引用传递][传入的参数值为:%d]", *num)
    *num++
    log.Printf("[引用传递][add2计算后的值为:%d]", *num)
}

func main() {
    num := 1
    log.Printf("[局部遍历的值:%d]", num)
    add1(num)
    time.Sleep(1 * time.Second)
    log.Printf("[局部遍历的值:%d]", num)
    add2(&num)
    time.Sleep(1 * time.Second)
    log.Printf("[局部遍历的值:%d]", num)
}

引用类型 ```go package main

import ( "log" )

// 引用类型 引用传递 func mod(s1 []int, m1 map[string]string) {

log.Printf("[引用传递][传入的参数为:%v %v]", s1, m1)
s1[0] = 100
m1["a"] = "a2"
log.Printf("[引用传递][函数内部处理完的值为:%v %v]", s1, m1)

} func main() { s1 := []int{1, 2, 3} m1 := map[string]string{"a": "a1", "b": "b1"} mod(s1, m1) log.Printf("[引用传递][函数外部的值为:%v %v]", s1, m1) }

## 匿名函数
- 匿名函数最大的用途是来模拟块级作用域,避免数据污染的。
> 不带参
```go
package main

import "fmt"

func main() {

   f := func() {
      fmt.Println("abdc")
   }
   f()
   fmt.Printf("%T", f)

}

带参数的

package main

import "fmt"

func main() {

   f := func(args string) {
      fmt.Println(args)
   }
   f("abdc")
   fmt.Printf("%T", f)

}

带返回值 ```go package main

import "fmt"

func main() {

f := func() string { return "abdc" } a := f() fmt.Printf("%T\n", f) fmt.Println(a)

}

> 返回多个匿名函数

```go
package main

import "fmt"

func FGen(x, y int) (func() int, func(int) int) {

    //求和的匿名函数
    sum := func() int {
        return x + y
    }

    // (x+y) *z 的匿名函数
    avg := func(z int) int {
        return (x + y) * z
    }
    return sum, avg
}

func main() {

    f1, f2 := FGen(1, 2)
    fmt.Println(f1())
    fmt.Println(f2(3))
}

闭包

  • 闭包:说白了就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕。
  • 不同闭包函数变量内部维护不是同一个变量
  • 所有的匿名函数都是闭包 ```go package main

import "fmt"

func Greeting() func(string) string { a := " 你好啊 " return func(s string) string { a += s return a } }

func main() {

g1 := Greeting()
g2 := Greeting()
fmt.Println(g1("小乙"))
fmt.Println(g1("李逵"))
fmt.Println(g1("宋江"))
fmt.Println(g2("宋江"))
fmt.Println(g2("李逵"))

}

![image](./pic/闭包.png)
- 累加器例子
```go
package main

import (
    "log"
)

func add1() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    log.Printf("[普通的累加器]:%d", sum)
}

func add2() func(int) int {
    // 自由变量
    sum := 0
    return func(i int) int {
        sum += i
        return sum
    }
}



func callCF() {
    f := add2()
    sum := 0
    for i := 0; i < 10; i++ {
        sum = f(i)
        log.Printf("[闭包的累加器]:%d", sum)
    }
}

func main() {

    add1()

    // 闭包的
    callCF()
}
  • 函数式编程的应用场景
    • 对于数据的长流程处理
    • 类似流水线,装配模式
    • 可以随时增删流程
    • 装饰器

递归函数必须使用闭包

递归函数定义

  • 递归指函数可以直接或间接的调用自身。
  • 递归函数通常有相同的结构:一个跳出条件和一个递归体
  • 所谓跳出条件就是根据传入的参数判断是否需要停止递归,而递归体则是函数自身所做的一些处理。
  • 斐波拉契数列
package main

import "log"

func fib(i int) int {
    if i == 0 || i == 1 {
        return i
    }
    return fib(i-1) + fib(i-2)
}

func main() {
    log.Println(fib(7))
    for i:=0;i<10;i++{
        log.Printf("[%d=%d]",i,fib(i))
    }
}
  • 1+2+....100
  • 从低位加从高位加 ```go package main

import ( "log" )

// 实现1+...+100 // 从低位加到高位 func sum1(num int) int { if num == 100 { return num } return num + sum1(num+1) }

// 从高位加到低位 func sum2(num int) int { if num == 1 { return num } return num + sum2(num-1) }

func main() { log.Printf("从低位加到高位 :%d", sum1(1)) log.Printf("从高位加到低位 :%d", sum2(100)) }

## defer语句
- 类似于栈,类比python中的 上下文管理器 `__exit__`
- Go 语言的 defer 会在当前函数返回前执行传入的函数
- 它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源

> defer 保证文件描述符被close
```go
f,err := os.Open(filename)
if err != nil {
    panic(err)
}
defer f.Close()

倒序打印num ```go package main

import "fmt"

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

> defer 预计算参数  和defer func()
- 使用defer打印时间差
```go
package main

import (
    "log"
    "time"
)

func main() {
    start := time.Now()
    log.Printf("开始时间为:%v", start)
    defer log.Printf("时间差:%v", time.Since(start))
    time.Sleep(3 * time.Second)

    log.Printf("函数结束")

    /*
    时间差不是3秒,而是很小
    2021/07/18 18:04:10 开始时间为:2021-07-18 18:04:10.6563346 +0800 CST m=+0.004339401
    2021/07/18 18:04:13 函数结束
    2021/07/18 18:04:13 时间差:43.6851ms

    */
}
  • 造成上述问题的原因
    • Go 语言中所有的函数调用都是传值的
    • 调用 defer 关键字会立刻拷贝函数中引用的外部参数 ,包括startAt 和time.Since中的Now
    • defer的函数在压栈的时候也会保存参数的值,并非在执行时取值。
  • 怎么解决这个问题:使用defer func() ```go package main

import ( "log" "time" )

func main() { start := time.Now() log.Printf("开始时间为:%v", start) defer func() { log.Printf("开始调用defer") log.Printf("时间差:%v", time.Since(start)) log.Printf("结束调用defer") }() time.Sleep(3 * time.Second)

log.Printf("函数结束")

/*
    2021/07/18 18:09:01 开始时间为:2021-07-18 18:09:01.7131174 +0800 CST m=+0.004520101
    2021/07/18 18:09:04 函数结束
    2021/07/18 18:09:04 开始调用defer
    2021/07/18 18:09:04 时间差:3.0482416s
    2021/07/18 18:09:04 结束调用defer

*/

}

- 因为拷贝的是函数指针


# defer 和return

- f1返回1 
```go
func f1() (res int) {
    defer func() {
        res++
    }()
    return 0
}

原理解读

  • return xxx这条语句不是一条原子操作
    • 先给返回值赋值
      • return 1要翻译成 res =1 +return
    • 然后调用defer语句
    • 最后返回调用函数中 ,空的return

```shell script 返回值 = xxx 调用defer 函数 空的return

> f1可以改造成
```go
func f11() (res int) {
    res = 0
    func() {
        res++
    }()
    return

}

f2改造成 ```go func f22() (res int) { t := 5 res = t // 真正的res返回值 赋值 func() { t = t + 5 }() return }

> f3改造成
```go
func f33() (res int) {
    res = 0 // 赋值指令
    func(res int) {
        res = res + 5 //值传递,深拷贝, 操作的是副本
    }(res)

    return // 空的return
}
  • f4的结果是什么
  • 为什么?
  • 改造成解释defer和return的顺序函数 ```go package main

import "fmt"

func f4() (t int) {

defer func() {
    t = t * 10
}()

return 1

}

func main() { fmt.Println(f4()) }

> f4 改造成这样
```go
func f44() (t int) {
    t = 1 // 先给返回值赋值
    func() {
        t = t * 10 // 匿名函数,引用闭包的自由变量 t ,会直接更改t的值
    }()

    return
}

全部的示例 ```go package main

import ( "fmt" "log" )

// 返回1 func f1() (res int) { defer func() { res++ }() return 0 }

func f11() (res int) { res = 0 func() { res++ }() return

}

// 匿名不带参 func f2() (res int) { t := 5 defer func() { t = t + 5 }() return t }

// f2可以改造为 func f22() (res int) { t := 5 res = t // 真正的res返回值 赋值 func() { t = t + 5 }() log.Printf("f22.t=%d",t) return }

// 匿名带参数 func f3() (res int) { defer func(res int) { res = res + 5 }(res) return res } func f33() (res int) { res = 0 // 赋值指令 func(res int) { res = res + 5 //值传递,深拷贝, 操作的是副本 }(res)

return // 空的return

}

func f4() (t int) {

defer func() {
    t = t * 10
}()

return 1

}

func f44() (t int) { t = 1 // 先给返回值赋值 func() { t = t * 10 // 匿名函数,引用闭包的自由变量 t ,会直接更改t的值 }()

return

}

func main() { fmt.Println(f1()) fmt.Println(f11()) fmt.Println(f2()) fmt.Println(f22()) fmt.Println(f3()) fmt.Println(f33()) fmt.Println(f4()) fmt.Println(f44()) }

```

defer 和 panic

  • panic会触发defer出栈执行
  • defer 最大的功能是 panic 后依然有效
  • 所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。

不用recovery捕获异常

使用recovery捕获异常

  • panic之后的defer 还没来得及压栈

defer中含有panic

  • 这个异常将会覆盖掉main中的异常panic("panic"),最后这个异常被第二个执行的defer捕获到。

defer中函数参数包含子函数

  • 先压栈1,再压栈2
  • 子函数在压栈是就要执行获取返回值作为参数
  • 因为压栈时需要连同函数地址、函数形参一同进栈
  • 所以调用顺序为 压栈f1,调用f3,压栈f2,调用f4 调用f2,f1

运算子函数

  • 看起来是根据string调用的函数
  • 其实是根据map的映射搞得
  • init函数默认被调用,其实是在包导入的时候