# Go 基础



### 参考链接

> \[Golang教程 - 菜鸟教程]\([Go 基础教程 - Golang教程 - 菜鸟教程](https://www.cainiaojc.com/golang/)l)
>
> [Go 系列教程 —— 34. 反射 - Go语言中文网 - Golang中文社区](https://studygolang.com/articles/13178)

### Hello world

```go
package main                   // 包名称
import "fmt"

func main() {                  // main: 函数名
    fmt.Println("Hello world") // Hello world
    fmt.Println("1+1=",1+1)    // 1+1= 2

    var name = "golong"        // 变量名
    fmt.Println(name)
}
```

### Go 标识符规则

* 标识符的名称必须以字母或下划线（\_）开头。并且名称中可能包含字母“ a-z”或“ A-Z”或数字0-9，以及字符“
* 标识符的名称不能以数字开头。
* 标识符的名称区分大小写。
* 关键字不能用作标识符名称。
* 标识符名称的长度没有限制，但是建议仅使用4到15个字母的最佳长度。

例如:

```go
// 有效的标识符:
_geeks23
geeks
gek23sd
Geeks
geeKs
geeks_geeks

// 无效的标识符:
212geeks
if
default
```

### Go 内置常量、类型、函数

```go
// 常量
true(真), false(假), iota(计数器), nil(空)

// 类型
int,     in和uint都包含相同的大小，无论是32位还是64位。
int8,    8位有符号整数
int16,   16位有符号整数
int32,   32位有符号整数
int64,   64位有符号整数
uint,    in和uint都包含相同的大小，无论是32位还是64位。
uint8,   8位无符号整数
uint16,  16位无符号整数
uint32,  32位无符号整数
uint64,  64位无符号整数
uintptr, 它是无符号整数类型。它的宽度未定义，但是可以容纳指针值的所有位。
float32, 
float64, 
complex128, 
complex64,
bool, 
byte,    它是int8的同义词。
rune,    它是int32的同义词，也表示Unicode代码点
string, 
error

// 函数
make,   分配并初始化切片、map、channel对象；
len,    返回长度，取决于具体类型；字符串返回字节数；channel返回缓存元素的个数；
cap,    返回容量，取决于具体类型；切片返回底层数组长度；channel返回缓存容量；
new,    分配内存，返回类型指针；不初始化切片，map，channel；
append, 将元素追加到切片，返回更新后的切片；若容量不足，则新分配一个底层数组；
copy,   切片复制，返回被复制的数量，即len(src)、len(dst)中较小的；支持字符串复制到字节切片；
close,  关闭channel；必须是双向channel或只发送channel；已关闭的channel在接收时第二参数返回false；
delete, 根据键删除map中的元素；
complex, 
real, 
imag, 
panic,  终止程序，引发异常；
recover defer函数中调用，捕获panic错误信息；
```

### Go 关键字(25个)

```go
break    
case    
chan    
const    
continue
default    
defer    
else    
fallthrough    
for
func    
go    
goto    
if    
import
interface    
map    
package    
range    
return
select    
struct    
switch    
type    
var
```

### GO 数据类型

* 基本类型：数字(int)，字符串(string)和布尔值(bool)属于此类别。
* 聚合类型：数组和结构属于此类别。
* 引用类型：指针，切片，map集合，函数和Channel属于此类别。
* 接口类型

### GO println和printf区别

* Println :可以打印出字符串，和变量
* Printf : 只可以打印出格式化的字符串,可以输出字符串类型的变量

```go
//使用字符串
package main 
import "fmt"

func main() { 

    //用于存储字符串的str变量
   str := "nhooo"

   //显示字符串的长度
   fmt.Println("字符串的长度:%d", len(str)) //字符串的长度:%d 5
   fmt.Printf("字符串的长度:%d", len(str))  //字符串的长度:5

   //显示字符串 
   fmt.Println("\n字符串是: %s", str)   // 字符串是: %s nhooo
   fmt.Printf("\n字符串是: %s", str)    // 字符串是: nhooo

   // 显示str变量的类型
   fmt.Println("\nstr的类型是: %T", str) // str的类型是: %T nhooo
   fmt.Printf("\nstr的类型是: %T", str)  // str的类型是: string
}
```

### Go 变量

> 变量命名规则： 变量名称必须以字母或下划线（\_）开头。并且名称中可能包含字母“ a-z”或“ A-Z”或数字0-9，以及字符" \_"

* 创建变量的方法
  * 使用var关键字
  * 使用短变量声明运算符（:=）---> 只能在局部使用，不能全局
*   声明变量

    ```go
    package main 
    import "fmt"

    func main() {
        // 方法一: 变量声明和初始化，显示类型
        var _var1 = 1

        // 方法二： 变量声明和初始化，指定变量类型，不声明值
        var _var2 int
        var _var3 string
        var _var4 float64

        // 方法三：同时声明和初始化多个同类型变量
        var _var5, _var6, _var7 int = 111, 222 , 333

        // 方法四：同时声明和初始化多个不同类型变量
        var _var8, _var9, _var10 = 2, "abc", 11.44
    ```

    ```
    // 方法五：短变量声明, 内置变量
    _var11 := 2
    _var12 := "3"
    _var13 := 11.11

    // 方法五：短变量声明, 多变量声明
    _var14, _var15, _var16 := 800, "NHOOO", 47.56

    fmt.Printf("变量类型: %T", _var1,_var2,_var3,_var4,_var5,_var6,_var7,_var8,_var9,_var10,_var11,_var12,_var13,_var14,_var15,_var16)
    ```

    }

    变量类型: int%!(EXTRA int=0, string=, float64=0, int=111, int=222, int=333, int=2, string=abc, float64=11.44, int=2, string=3, float64=11.11, int=800, string=NHOOO, float64=47.56)

````
## Go 变量作用域

- 局部变量（在块或函数内部声明）
- 全局变量（在块或函数外部声明）

> 局部变量: 函数内定义的变量，函数外无法调用
> 全局变量: 函数外定义的变量，可在任何部分访问
> 如果同时存在同一的变量名称，局部变量 > 全局变量

```go
//全局变量
package main
import "fmt"

// 全局变量声明
var _var1 int = 100

func main() {

  // 主函数内部的局部变量
  var _var2 int = 200

  //显示全局变量
  fmt.Printf("全局变量 _var1 的值是 : %d\n", _var1 ) // 100

  //显示局部变量
  fmt.Printf("局部变量 _var2 的值是 : %d\n", _var2 ) // 200

  //调用函数
  display()

}

func display() {
  // 显示全局变量
  fmt.Printf("全局变量 _var1 的值是 : %d\n", _var1 ) // 100

}
````

### Go 语言常量

> 常量就像变量一样声明，但是使用 const 关键字作为前缀来声明具有特定类型的常量。不能使用 := 语法声明。

* 典型

```go
package main 
import "fmt"

func main() { 
    const mydefault = true     // 赋值声明变量
    type myBool bool           

    var new1Bool = mydefault
    var new2Bool myBool = mydefault

    // new1Bool = new2Bool  不允许这样使用
    fmt.Println(new1Bool) 
    fmt.Println(new2Bool) 
}
```

### Go 运算符

#### 算数运算符

> \+ - \* / %
>
> % 当第一个操作数除以第二个操作数时，'％'运算符返回余数。例如，x％y。

```go
package main 
import "fmt"

func main() { 
    a := 1
    b := 2
    fmt.Println(a+b) // 3
    fmt.Println(a-b) // -1
    fmt.Println(a*b) // 2
    fmt.Println(a/b) // 0
    fmt.Println(a%b) // 1
}
```

#### 关系运算符

> \== , != , > , < , >= , <=

```go
package main 
import "fmt"

func main() { 
    a := 1
    b := 2
    fmt.Println(a==b) // 返回： false
    fmt.Println(a!=b) // 返回： true
    fmt.Println(a>b)  // 返回： false
    fmt.Println(a<b)  // 返回： true
    fmt.Println(a>=b) // 返回： false
    fmt.Println(a<=b) // 返回： true
}
```

#### 逻辑运算符

> &&(逻辑AND), ||(逻辑或), !(逻辑非)

```go
package main 
import "fmt"

func main() { 
    p := 1
    q := 2

    if p != q && p <= q {
        fmt.Println("True") // True
    }

    if p != q || p <= q {
        fmt.Println("True") // True
    }

    if !(p == q) {
        fmt.Println("True") // True
    }


}
```

#### 按位运算符(没看懂)

* ＆（按位与）：将两个数字作为操作数，并对两个数字的每一位进行“与”运算。仅当两个位均为1时，AND的结果才为1。
* \| （按位或）：将两个数字作为操作数，并对两个数字的每一位进行“或”运算。两个位中的任何一个为1，OR的结果为1。
* ^（按位XOR）：将两个数字作为操作数，并对两个数字的每一位进行XOR。如果两个位不同，则XOR的结果为1。
* <<（左移）：取两个数字，左移第一个操作数的位，第二个操作数决定移位的位数。
* > > （右移）：取两个数字，右移第一个操作数的位，第二个操作数决定移位的位数。
* ＆^（AND NOT）：(按位清除)运算符，该运算符的实际操作为&(^)操作。

```go
package main 
import "fmt"

func main() { 
    p := 456
    q := 793

    fmt.Printf("计算结果是： p & q = %d", p & q)    // 计算结果是： p & q = 264
    fmt.Printf("\n计算结果是： p | q = %d", p | q)  // 计算结果是： p | q = 985
    fmt.Printf("\n计算结果是： p ^ q = %d", p ^ q)  // 计算结果是： p ^ q = 721
    fmt.Printf("\n计算结果是： p << q = %d", p << q)  // 计算结果是： p << q = 0
    fmt.Printf("\n计算结果是： p >> q = %d", p >> q)  // 计算结果是： p >> q = 0
    fmt.Printf("\n计算结果是： p &^ q = %d", p &^ q)  // 计算结果是： p &^ q = 192
}
```

#### 赋值运算符

> 赋值运算符用于为变量赋值(运算) 注意: 右侧的值必须与左侧的变量具有相同的数据类型，否则编译器将抛出错误

> \= , += , -= , \*= , /= , %= , &= , ^= , |=

```go
package main 
import "fmt"

func main() { 
    p := 1
    q := 2

   // “=”(简单赋值) 
   p = q
   fmt.Println(p)  // 2

   // “+=”(加法赋值) 
   p += q 
   fmt.Println(p) // 4, 由于上面运算已经赋值,所以是 p=2+q

   //“-=”(减法赋值) 
   p -= q 
   fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4-q

   // “*=”(乘法赋值) 
   p *= q 
   fmt.Println(p) // 4,由于上面运算已经赋值,所以是 p=2*q

   // “/=”(除法赋值) 
   p /= q 
   fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4/2

    // “%=”(求模赋值) 
    p %= q 
   fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4/2
}
```

#### 杂项运算符

* ＆：此运算符返回变量的地址。
* \*：此运算符提供指向变量的指针。
* <-：该运算符的名称为接收。它用于从通道接收值。

```go
package main 
import "fmt"

func main() { 
    a := 23

    b := &a          
    fmt.Println(*b)  // 23

    *b = 78
    fmt.Println(a)   // 78
}
```

### Go 类型转换

> 注意：由于Golang具有强大的类型系统，因此不允许在表达式中混合使用数字类型（例如加，减，乘，除等），并且不允许在两个混合类型之间执行赋值类型。

```go
package main 
import "fmt"


func main() {
/*
    // 显示类型转换
    var a int = 985
    var b uint = uint(a)
    var c int64 = int64(a)
    var d float64 = float64(a)
    fmt.Printf("转换类型： %T",b,c,d) // 转换类型： uint%!(EXTRA int64=985, float64=985)
*/

/*
    // 计算平均值
    var a int = 446
    var b int = 23
    var c float32
    c = float32(a) / float32(b)
    fmt.Printf("平均值 = %f",c) // 平均值 = 19.391304
*/



}
```

### Go 条件判断

```go
package main 
import "fmt"

func main() {
    var a int = 100

    // if 语句
    if(condition) {
       //condition为真,执行
    }

    // if...else 语句
    if (condition) {
        // if条件为true，执行此代码块
    } else {
        // if条件为false，执行此代码块
    }


    // 嵌套if 语句
    if (condition1) {
        // 当条件1为真时执行
        if (condition2) {
            // 当条件2为真时执行
        }
    }

    // if...else...if 语句
    if(condition_1) {
        //condition_1为true,执行这里的代码块
    } else if(condition_2) {
        //condition_2为true,执行这里的代码块
    }else {
        //没有条件为true时,执行这里代码块
    }

}
```

### Go 循环语句

> 如果数组，字符串，切片或map集合为空，则for循环不会抛出错误并继续其流程。换句话说，如果数组，字符串，切片或map为nil，则for循环的迭代次数为零。

* 语法:

```go
for initialization; condition; post{
       // 语句....
}
```

* initialization: 可选,循环开始之前执行(如变量声明，递增，赋值，函数)
* condition: 布尔表达式，该表达式在循环的每次迭代开始时计算。如果条件语句的值为true，则执行循环。
* post: for循环体之后执行。在post语句之后，条件语句再次计算条件语句的值是否为false，然后循环结束

#### for简单循环

```go
package main
import "fmt"

func main() {
    // initialization 是 i := 0 ， condition 是 i < 4 ; post 是 i++
    // 意思: 定义变量i=0,从0开始，直到 i < 4 为真，每次循环之后 i+1
    for i :=0; i < 4; i++ {
        fmt.Println(i)
    }
}

// 输出: 
0
1
2
3
```

#### for无线循环

```go
package main 
import "fmt"

func main() { 

    // 无限循环 
    for { 
      fmt.Printf("nhooo\n")   
    } 

}
```

#### for循环用作while循环

```go
package main 

import "fmt"

func main() { 

      //while循环
      //for循环执行到
      //i <3条件为真
    i:= 0 
    for i < 3 { 
       i += 2 
    } 
  fmt.Println(i)  
}
```

#### for循环数组

```go
package main
import "fmt"

func main() {
    // 定义一个数组
    _var := bytestring{"a","b","c"}

    // index和j是分配迭代值的变量。它们也称为迭代变量。
    // 第二个变量，即j是可选的。
    // 范围表达式在循环开始之前被评估一次。


    // index和j存储 _var 的值
    // index存储单个字符串和的索引号
    // j存储给定数组的单个字符串
    for index,j := range _var{
        fmt.Println(index, j)
    }
}

// 输出
0 a
1 b
2 c
```

#### for循环字符串

```go
package main
import "fmt"

func main() {
    for index, j := range "abcd" {
        fmt.Println(index, j ,string(j))
        // 0 97 a
        // 1 98 b
        // 2 99 c
        // 3 100 d
    }

    for index, j := range "abcd" {
        fmt.Printf("%U 的索引值为 %d\n", j, index,string(j))
        // %!(EXTRA string=a)U+0062 的索引值为 1
        // %!(EXTRA string=b)U+0063 的索引值为 2
        // %!(EXTRA string=c)U+0064 的索引值为 3
        // %!(EXTRA string=d)
    }
}
```

#### for循环map的键和值对

```go
package main
import "fmt"

func main() {
    res := map[int]string{
        111: "a",
        222: "b",
        333: "c",
    }
    for k, v := range res {
        fmt.Println(k, v)
    }
}


// 输出
111 a
222 b
333 c
```

#### for通道

```go
package main

import "fmt"

func main() {

    // 使用 channel
    chnl := make(chan int)
    go func() {
        chnl <- 100
        chnl <- 1000
        chnl <- 10000
        chnl <- 100000
        close(chnl)
    }()
    for i := range chnl {
        fmt.Println(i)
    }

}
```

#### for控制语句

* break ---> 终止其所在的循环或语句
* continue ---> 在循环中，表示结束本次循环，进行下次循环。
* goto ---> 表示无条件跳转到标号处执行。

```go
package main 
import "fmt"

func main() {
    _x := 0
    Lable1: for _x < 8 {
        if(_x == 5) {
            _x = _x + 1
            goto Lable1
        }
        fmt.Println("值为:", _x)
        _x++;
    }
}

// 输出
值为: 0
值为: 1
值为: 2
值为: 3
值为: 4
值为: 6
值为: 7
```

### Go Switch几种用法

* 表达式 switch
* 类型 switch

语法:

```go
switch optstatement; optexpression{
    case expression1: Statement..
    case expression2: Statement..
    ...
    default: Statement..
}
```

#### 示例一: 可选语句结合表达式

```go
package main 
import "fmt"

// 同时使用两种语句, 如可选语句, day:=4 和表达式 如：day
func main() {
    switch day := 2; day{
        case 1:
            fmt.Println(1)
        case 2:
            fmt.Println(2)    // 输出2
        case 3:
            fmt.Println(3)
        default:
            fmt.Println("I don't know")

    }
}
```

#### 示例二: 无可选，无表达式

> switch语句不带可选语句和表达式

```go
package main 
import "fmt"

func main() {
    var value int = 2

    switch {
        case value == 1:
            fmt.Println(1)
        case value == 2:
            fmt.Println(2)
        case value == 3:
            fmt.Println(3)
        default:
            fmt.Println("I don't know")
    }
}
```

#### 示例三: case多个值

```go
package main

import "fmt"

func main() {
    var value string = "e"

    //没有默认语句的switch语句，case语句中的多个值
    switch value {
        case "a":
            fmt.Println("aaa")
        case "b", "c":
            fmt.Println("bbb")
        case "d", "e", "f":
            fmt.Println("ccc")
    }
}
```

#### 示例四: 类型switch

```go
package main
import (
    "fmt"
)
func outType(v interface{}) {
    switch v.(type) {
    case int:
        fmt.Println(v, "is int")
    case string:
        fmt.Println(v, "is string")
    case bool:
        fmt.Println(v, "is bool")
    }
}
func main() {
    outType(1024)   // 1024 is int
    outType("pig")   // pig is string
    outType(true)   // true is bool
}
```

### Go 避免死锁

*   语法

    ```go
    select{
      case SendOrReceive1: // 语句
      case SendOrReceive2: // 语句
      case SendOrReceive3: // 语句
      .......
      default: // Statement
    }
    ```

设置默认值

```go
package main
import "fmt"

func main() {
    // 创建通道
    c := make(chan int)
    select {
    case <-c:
    default:
        fmt.Println("default")
    }
}
```

### Go 函数

#### 简单函数

**语法**

```go
func function_name(Parameter_list)(Return_type){
    // function body.....
}
```

* func: Go关键字，用于创建函数
* function\_name: 函数名称
* Parmeter\_list: 包含函数参数名称和类型
* Return\_type: 可选，包含函数返回值类型，如果在函数中使用return\_type，则必须在函数中使用return语句。

```go
package main
import "fmt"

// 此函数需要传两个 int 类型的值， 返回的值也是 int 类型
func abc(length, width int) int { 
    res := length + width
    return res
}



func main() {
    fmt.Println(abc(10,10)) // 调用函数
}
```

#### 可变参函数

* 当您要在函数中传递切片时，使用可变参数函数。
* 当我们不知道参数的数量时，使用可变参数函数。
* 在程序中使用可变参数函数时，它可以增加程序的可读性。

```go
package main
import (
    "fmt"
    "strings"
)


// 可变参数函数联接字符串
func jsonStr(element ...string) string{
    return strings.Join(element, "-") 
}



func main() {
    // 无参数
    fmt.Println(jsonStr())

    // 多参数
    fmt.Println(jsonStr("a","b"))
    fmt.Println(jsonStr("a","b","c","d"))

    //在可变函数中传递一个切片
    element:= []string{"geeks", "FOR", "geeks"} 
    fmt.Println(jsonStr(element...)) 
}
```

#### 匿名函数

* 定义格式

```go
func(参数列表)(返回参数列表){
    函数体
}
```

* 在定义时调用匿名函数

```go
func(data int) {
    fmt.Println("hello", data)
}(100)  // (100)，表示对匿名函数进行调用，传递参数为 100。
```

* 将匿名函数赋值给变量

```go
// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}

f(100)
```

* 匿名函数用作回调函数

> 实现对切片的遍历操作，遍历中访问每个元素的操作使用匿名函数来实现，用户传入不同的匿名函数体可以实现对元素不同的遍历操作

* 简单实用

```go
package main
import (
    "fmt"
)

func main() {
    // 匿名函数，无参数
    func() {
        fmt.Println("hello world")
    }()

    // 匿名函数，有参数
    func(ele string) {
        fmt.Println(ele)
    }('hello world')

}
```

* 匿名函数作为参数传递给其他函数。

```go
package main
import (
    "fmt"
)

// 匿名函数作为参数传递  
func GFG(i func(p, q string)string) {
    fmt.Println(i ("Geeks", "for"))
}


func main() {
    // 匿名函数，传参
    value := func(p, q string) string{
        return p + q + "mmp"
    }
    GFG(value)
}
```

* 从另一个函数返回匿名函数。(常用于回调函数)

```go
package main
import (
    "fmt"
)

// 返回匿名函数
func GFG() func(i, j string)string{
    myf := func(i,j string)string{
        return i + j + "hello world"
    }
    return myf
}


func main() {
    // 调用匿名函数并传值
    value := GFG()
    fmt.Println(value("weblcome ", "to "))
}
```

### Go 函数调用

#### 按值调用

> 函数内部进行的任何更改都不会反映在调用者的实际参数中

```go
package main

import "fmt"

func modify(Z int) {
    fmt.Println(Z)  // 10
    Z = 79
}

func main() {
    var Z int = 10
    fmt.Println("调用前:", Z)  // 10

    modify(Z) // 调用修改函数

    fmt.Println("调用后:", Z) // 10
}
```

#### 引用调用

* 指针概念： 在函数调用中，我们传递变量的地址，并使用解引用运算符\*修改值

```go
package main

import (
    "fmt"
)



func modify(Z *int) {
    *Z = 70
}


func main() {
    var Zz int = 10

    modify(&Zz) // 通过引用调用传递变量Z地址,指针方式

    fmt.Println(Zz) // 70
}
```

#### 函数返回值

* 首先先看下函数语法

```go
func function_name(parameter_list)(return_type_list){
     // code...
}
```

* function\_name：它是函数的名称。
* parameter-list：它包含函数参数的名称和类型。
* return\_type\_list：这是可选的，它包含函数返回的值的类型。如果在函数中使用return\_type，则必须在函数中使用return语句。

#### 返回多个值

```go
package main

import (
    "fmt"
)


//  myfunc返回3个int类型的值
func myfunc(p, q int) (int,int,int) {
    return p - q, p * q, p + q
}


func main() {
    var _var1, _var2, _var3 = myfunc(5,7)
    fmt.Println(_var1,_var2,_var3)  // -2 35 12
}
```

#### 返回值命名

> 允许为返回值提供名称。你也可以在代码中使用这些变量名。

* 语法

```go
// name1和name2是返回值的名称，而para1和para2是函数的参数。

func function_name(para1, para2 int)(name1 int, name2 int){
    // code...
}

或

func function_name(para1, para2 int)(name1, name2 int){
   // code...
}
```

```go
package main

import (
    "fmt"
)


func myfunc(p,q int) (age1 int, age2 int) {
    age1 = p + q
    age2 = p * q
    return   // Go编译器将自动返回参数
}

func main() {
    var res1, res2 = myfunc(2, 4)

    fmt.Println(res1, res2)  // 6 8
}
```

### Go 空白标识符

> 只需使用\_（下划线）。它允许编译器忽略该特定变量的错误(declared and not used)。

```go
package main

import "fmt"

func main() {

    //调用函数
    //函数返回两个值
    //分配给mul和空白标识符（忽略）
    mul, _ := mul_div(105, 7)

    //只使用mul变量
    fmt.Println("105 x 7 = ", mul)
}

//函数返回两个
//整数类型的值
func mul_div(n1 int, n2 int) (int, int) {
    //返回值
    return n1 * n2, n1 / n2
}
```

### Go 上下文函数

> init()函数的主要目的是初始化无法在全局上下文中初始化的全局变量。

```go
package main 

import "fmt"

//多个init()函数,必须放在 main 之前
func init() { 
    fmt.Println("Welcome to init() function") 
} 

func init() { 
    fmt.Println("Hello! init() function") 
} 

func main() { 
    fmt.Println("Welcome to main() function") 
}
```

### Go Defer关键字

* 同一程序中允许多个defer语句，并且它们按LIFO（后进先出）顺序执行
* 在defer语句中，将在执行defer语句时（而不是在调用它们时）评估参数。
* defer语句通常用于确保在完成文件处理后关闭文件，关闭通道或捕获程序中的紧急情况。
* 语法

```go
// 函数
defer func func_name(parameter_list Type) return_type{
    // Code
}

// 方法
defer func (receiver Type) method_name(parameter_list){
    // Code
}

defer func (parameter_list)(return_type){
    // code
}()
```

* 后进先出延迟执行

```go
package main

import "fmt"

func add(a1, a2 int) int {
    res := a1 + a2 
    fmt.Println("Result: ", res)
    return 0
}


func main() {
    fmt.Println("Start")

    // 多个延迟,已LIFO顺序执行
    defer fmt.Println("End")
    defer add(1,2)
    defer add(3,4)
    fmt.Println("烟")
}


// 执行顺序
Start
烟
Result:  7
Result:  3
End
```

### Go 方法(看不懂)

### Go 结构体(看不懂)

### Go 数组

* 语法

```go
var array_name[length]Type
或
var array_name[length]Typle{item1, item2, item3, ...itemN}
```

#### var创建数组

```go
package main

import "fmt"

func main() {
    // 创建字符串类型数组
    var arr [3]string

    // 使用索引分配元素
    arr[0] = "a"
    arr[1] = "b"
    arr[2] = "c"

    // 使用索引输出元素
    fmt.Println(arr[0],arr[1],arr[2]) // a b c
}
```

#### 简声明创建数组

```go
array_name:= [length]Type{item1, item2, item3,...itemN}[object Object]
```

```go
package main

import "fmt"



func main() {

    // 数组的简写声明
    arr := [4]string{"a","b","c"}

    // for 循环访问数组
    for i := 0; i < len(arr); i++ {
        fmt.Println(arr[i])
    }
}
```

#### 多维数组

> 数组是一维的，但是允许创建多维数组。多维数组是相同类型数组的数组

* 语法

```go
Array_name[Length1][Length2]..[LengthN]Type
```

```go
package main

import "fmt"



func main() {


    // 使用 var 关键字创建二维数组
    // var arr1 [2][2]int
    // arr1[0][0] = 100
    // arr1[0][1] = 200
    // arr1[1][0] = 300
    // arr1[1][1] = 400

    // 使用简单声明二维数组
    arr := [3][3]string {
                            {"a","b","c"},
                            {"1","2","3"}, 
                            {"m","n","p"},
                        }

    for x := 0; x < len(arr); x++ {
        for y := 0; y < len(arr); y++ {
            fmt.Println(arr[x][y])
        }
        fmt.Println("------------")
    }
}

// 输出
a
b
c
------------
1
2
3
------------
m
n
p
------------
```

#### 省略号

> ... 在长度位置处可见，则数组的长度由初始化的元素确定

```go
//数组中省略号的使用方法
package main

import "fmt"

func main() {

    //创建大小已确定的数组,根据其中元素的数量,使用省略号
    myarray := [...]string{"a", "b", "c", "d"}

    fmt.Println("数组元素: ", myarray)  // 数组元素:  [a b c d]

    //数组的长度 由...决定 使用len()方法
    fmt.Println("数组的长度为:", len(myarray)) // 数组的长度为: 4
}
```

#### 数组比较

```go
package main

import "fmt"

func main() {

    arr1 := [3]int{9, 7, 6}
    arr2 := [...]int{9, 7, 6}
    arr3 := [...]int{6, 7, 9}

    //使用==运算符比较数组
    fmt.Println(arr1 == arr2) // true
    fmt.Println(arr1 == arr3) // false

}
```

### Go Defer关键字

* 同一程序中允许多个defer语句，并且它们按LIFO（后进先出）顺序执行
* 在defer语句中，将在执行defer语句时（而不是在调用它们时）评估参数。
* defer语句通常用于确保在完成文件处理后关闭文件，关闭通道或捕获程序中的紧急情况。
* 语法

```go
// 函数
defer func func_name(parameter_list Type) return_type{
    // Code
}

// 方法
defer func (receiver Type) method_name(parameter_list){
    // Code
}

defer func (parameter_list)(return_type){
    // code
}()
```

* 后进先出延迟执行

```go
package main

import "fmt"

func add(a1, a2 int) int {
    res := a1 + a2 
    fmt.Println("Result: ", res)
    return 0
}


func main() {
    fmt.Println("Start")

    // 多个延迟,已LIFO顺序执行
    defer fmt.Println("End")
    defer add(1,2)
    defer add(3,4)
    fmt.Println("烟")
}


// 执行顺序
Start
烟
Result:  7
Result:  3
End
```

### Go 方法(看不懂)

### Go 结构体(看不懂)

### Go 数组

* 语法

```go
var array_name[length]Type
或
var array_name[length]Typle{item1, item2, item3, ...itemN}
```

#### 使用var关键字创建数组

```go
package main

import "fmt"

func main() {
    // 创建字符串类型数组
    var arr [3]string

    // 使用索引分配元素
    arr[0] = "a"
    arr[1] = "b"
    arr[2] = "c"

    // 使用索引输出元素
    fmt.Println(arr[0],arr[1],arr[2]) // a b c
}
```

#### 使用简声明

```go
array_name:= [length]Type{item1, item2, item3,...itemN}[object Object]
```

```go
package main

import "fmt"



func main() {

    // 数组的简写声明
    arr := [4]string{"a","b","c"}

    // for 循环访问数组
    for i := 0; i < len(arr); i++ {
        fmt.Println(arr[i])
    }
}
```

#### 多维数组

> 数组是一维的，但是允许创建多维数组。多维数组是相同类型数组的数组

* 语法

```go
Array_name[Length1][Length2]..[LengthN]Type
```

```go
package main

import "fmt"



func main() {


    // 使用 var 关键字创建二维数组
    // var arr1 [2][2]int
    // arr1[0][0] = 100
    // arr1[0][1] = 200
    // arr1[1][0] = 300
    // arr1[1][1] = 400

    // 使用简单声明二维数组
    arr := [3][3]string {
                            {"a","b","c"},
                            {"1","2","3"}, 
                            {"m","n","p"},
                        }

    for x := 0; x < len(arr); x++ {
        for y := 0; y < len(arr); y++ {
            fmt.Println(arr[x][y])
        }
        fmt.Println("------------")
    }
}

// 输出
a
b
c
------------
1
2
3
------------
m
n
p
------------
```

#### 省略号

> ... 在长度位置处可见，则数组的长度由初始化的元素确定

```go
//数组中省略号的使用方法
package main

import "fmt"

func main() {

    //创建大小已确定的数组,根据其中元素的数量,使用省略号
    myarray := [...]string{"a", "b", "c", "d"}

    fmt.Println("数组元素: ", myarray)  // 数组元素:  [a b c d]

    //数组的长度 由...决定 使用len()方法
    fmt.Println("数组的长度为:", len(myarray)) // 数组的长度为: 4
}
```

#### 数组比较

```go
package main

import "fmt"

func main() {

    arr1 := [3]int{9, 7, 6}
    arr2 := [...]int{9, 7, 6}
    arr3 := [...]int{6, 7, 9}

    //使用==运算符比较数组
    fmt.Println(arr1 == arr2) // true
    fmt.Println(arr1 == arr3) // false

}
```

#### 数组复制

* 语法

```go
// 按值创建数组的副本， 副改变，主不变
arr := arr1


// 通过引用创建数组的副本, 副本改变，主数组也改变
arr := &arr1
```

#### 数组作为函数参数

> 将1或多维数组传递给该函数

* 语法

```go
//对于指定大小的数组
func function_name(variable_name [size]type){
// Code
}

//对于无大小的数组
func function_name(variable_name []type){
// Code
}
```

```go
package main 

import "fmt"

func myfunc(a [6]int, size int) int {  // 接受两个参数，第一个参数6位int类型，第二个参数int类型, 返回int类型
    var k, val, r int

    for k = 0; k < size; k++ {
        val += a[k]
    }

    r = val / size
    return r
}


func main() {
    //创建数组
    var arr = [6]int{1,2,3,4,5,6}

    // 初始化数组，并将数组作为参数传递
    var res int = myfunc(arr, 6)
    fmt.Println(res)
}
```

### 切片的使用

* 数组是固定大小
* 切片大小可伸缩
* 在内部，切片和数组相连，切片是对基础数组的引用
* 切片中的第一个索引位置始终为0，而最后一个索引位置将为（切片的长度– 1）。
* 切片声明： \[]T 或 \[]T{} 或 \[]T{value1, value2, value3, ...value n}

```go
package main
import "fmt"

func main() {
    // 定义一个数组，数组长度最大是8, 数组索引下标从0开始
    arr := [8]string{"a","b","c","d","e","f","g"}
    fmt.Println("数组：",arr) // 数组： [a b c d e f g ]

    // 如何定义切片
    // 1、使用var关键字,创建切片
    // var my_slice_1 = []string{"nhooos", "for", "nhooos"}

    // 2、创建切片，使用简写声明
    // my_slice_2 := []int{12, 45, 67, 56, 43, 34, 45}

    // 切片数组
    myslice := arr[1:3] // 切片下标1开始，3结束，不包括3,也就是长度2
    fmt.Println("切片:",myslice)           // 切片: [b c]
    fmt.Println("切片长度:", len(myslice)) // 切片长度：2 (3-1,两个数值)
    fmt.Println("切片容量:",cap(myslice))  // 切片容量: 7 (最多存储)

    fmt.Println(arr[0:]) // [a b c d e f g ]
    fmt.Println(arr[:0]) // []
    fmt.Println(arr[:3]) // [a b c]
    fmt.Println(arr[:])  // [a b c d e f g ]
}
```

#### make()函数

> make()函数用于创建一个空切片

* 语法：

> func make(\[]T, len, cap) \[]T ;
>
> \[]T 类型， len 长度, cap 容量(可选,默认和长度一样))

```go
package main

import "fmt"

// 语法： func make([]T, len, cap) []T  ;  []T 类型， len 长度, cap 容量(可选,默认和长度一样))


func main() {
    var _slice = make([]int, 4, 7)
    fmt.Printf("切片内容: %v,\n切片长度: %d, \n切片容量: %d\n", _slice, len(_slice), cap(_slice))

// 切片内容: [0 0 0 0],
// 切片长度: 4, 
// 切片容量: 7

}
```

#### 遍历切片方法

```go
package main
import "fmt"

func main() {

    // 创建切片， 并遍历切片
    _slice2 := []string{"a","b","c"}

    // 方法一
    for s := 0; s < len(_slice2); s++ {
        fmt.Println(_slice2[s])
    }

    // 方法
    // 如果您不想获取元素的索引值，则可以使用空格（_）代替索引变量
    for _, v := range _slice2 {
        fmt.Println(v)
    }
}
```

#### 多维切片使用

```go
package main

import "fmt"



func main() {

    // 多维切片
    _slice3 := [][]string{
        []string{"a","aa"},
        []string{"b","bb"},
        {"c","cc"},
        {"d","dd"},
    }

    fmt.Println(_slice3)


}
```

#### 切片比较判断

* 判断两个\[]byte是否相等

```go
package main

import (
    "bytes"
    "fmt"
)



func main() {

    // 判断两个[]byte是否相等
    a := []byte{0,1,2,3}
    b := []byte{0,1,2,3}
    c := []byte{1,1,2,3}

    fmt.Println(bytes.Equal(a,b))  // true
    fmt.Println(bytes.Equal(a,c))  // false

}
```

* 使用reflect判断slice是否相等

> 深度比较两个对象包括它们内部包含的元素是否都相等： 语法: func DeepEqual(x, y interface{}) bool

* 牺牲了性能

```go
package main

import (
    "reflect"
    "fmt"
)

func main() {

    a := []int{1, 2, 3, 4}
    b := []int{1, 3, 2, 4}
    c := []int{1, 2, 3, 4}

    // 见证黑魔法
    fmt.Println(reflect.DeepEqual(a,b)) // false
    fmt.Println(reflect.DeepEqual(a,c)) // true

}
```

* 手写切片是否相等判断

> 性能比 reflect 快

```go
package main

import (
    "fmt"
)

// 以 int 类型为
func testEq(var1, var2 []int) bool {
    // 如果切片为空，那么另一个切片也必须为空
    if(var1 == nil) != (var2 == nil) {
        return false;
    }

    // 判断切片长度,如果两个切片长度不相等
    if len(var1) != len(var2) {
        return false;
    }

    // for 循环遍历，通过下标判断是否一样
    for i := range var1 {
        if var1[i] != var2[i] {
            return false
        }
    }

    // 如果都通过，则返回 true
    return true;
}



func main() {

    a := []int{1, 2, 3, 4}
    b := []int{1, 3, 2, 4}
    c := []int{1, 2, 3, 4}

    // 见证黑魔法
    fmt.Println(testEq(a,b)) // false
    fmt.Println(testEq(a,c)) // true

}
```

#### 切片排序

> Go语言的标准库提供了sort包，其中包含用于对int，float64和字符串切片进行排序的不同类型的排序方法。 这些函数始终按升序对可用元素进行切片排序

```go
package main 

import ( 
    "fmt"
    "sort"
) 

func main() { 

    //创建切片
    slc1 := []string{"Python", "Java", "C#", "Go", "Ruby"} 
    slc2 := []int{45, 67, 23, 90, 33, 21, 56, 78, 89} 


    //切片使用排序函数
    sort.Strings(slc1) 
    sort.Ints(slc2) 

    // IntsAreSorted 判断是否已经按照升序排序，如果是返回true, 如果不是返回false
    fmt.Println(sort.IntsAreSorted(slc2))

    fmt.Println("\n排序后:") 
    fmt.Println("Slice 1: ", slc1) // Slice 1:  [C# Go Java Python Ruby]
    fmt.Println("Slice 2: ", slc2) // Slice 2:  [21 23 33 45 56 67 78 89 90]

}
```

#### 切片分割

*   语法

    ```go
    func Split(o_slice, sep []byte) [][]byte
    o_slice是字节片
    sep是分隔符。

    如果sep为空，则它将在每个UTF-8序列之后拆分
    ```

#### 切片传递给函数

```go
package main 

import "fmt"


func modify(sls []int) {
    sls[0] = 90
}


func main() {
    a := []int{89,90,91}
    modify(a[:])
    fmt.Println(a)    // [90 90 91]
}
```

### 字符串操作

#### 字符串比较

```go
package main

import (
    "strings"
    "fmt"
)

func main() {

    a := "aabb"
    b := "hello"

    // 方式一，没有比较运算符来的方便
    fmt.Println(strings.Compare(a,b)) // a < b, 返回 -1
    fmt.Println(strings.Compare(a,a)) // a = a, 返回 0
    fmt.Println(strings.Compare(b,a)) // b > a, 返回 1

    // 方式二，使用比较运算符
    fmt.Println(a == b)               // 不相等，返回 false
    fmt.Println(a > b)                // a 没有 b 字符串多,返回 false
    fmt.Println(a < b)                // b 比 a 字符串多,返回 true

    // 方式三，使用 strings.EqualFold
    fmt.Println(strings.EqualFold("aa","aa"))    // 返回 true
    fmt.Println(strings.EqualFold("aa","aabb"))  // 返回 false
}
```

#### 是否存在某个字符或子串

* 语法

```go
// 子串 substr 在 s 中，返回 true          ---> 整体字符必须在 s 中存在
func Contains(s, substr string) bool

// chars 中任何一个 Unicode 代码点在 s 中，返回 true ---> 如果当中随便一个字符在 s 中，那么就返回 true
func ContainsAny(s, chars string) bool

// Unicode 代码点 r 在 s 中，返回 true
func ContainsRune(s string, r rune) bool
```

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Contains("team", "t"))            // true
    fmt.Println(strings.Contains("team", "ta"))           // false

    fmt.Println(strings.ContainsAny("team", "i"))         // false 
    fmt.Println(strings.ContainsAny("failure", "u & i"))  // true

}
```

#### 子串出现次数 ( 字符串匹配 )

> 当 sep 为空时，Count 的返回值是：utf8.RuneCountInString(s) + 1 Count 是计算子串在字符串中出现的无重叠的次数

* 语法

```go
func Count(s, sep string) int
```

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Count("fivevev", "vev")) // 1 , 无重叠次数
    fmt.Println(strings.Count("aba", "a")) // 2， 字符出现的次数
    fmt.Println(strings.Count("aba", ""))  // 4， sep步数为空，则 +1
    fmt.Println(len("aa"))  // 2， 字符数量 
}
```

#### Fields 和 FieldsFunc

> 该包提供了六个三组分割函数：Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。

* 语法

```go
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
```

* Fields

> 用一个或多个连续的空格分隔字符串 s，返回子字符串的数组（slice）如果字符串 s 只包含空格，则返回空列表 (\[]string 的长度为 0）

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Printf("Field:  %q", strings.Fields(" a b c"))
}

// 结果
Field:  ["a" "b" "c"]
```

* FieldsFunc

> 可以通过回调函数来指定分隔字符串 s 的字符， 也可自定义回调

```go
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    fmt.Println(strings.FieldsFunc("  foo bar  baz   ", unicode.IsSpace))
}

// 结果
[foo bar baz]
```

* Split 和 SplitAfter、 SplitN 和 SplitAfterN

> 这四个函数都是通过 sep 进行分割，返回\[]string。如果 sep 为空，相当于分成一个个的 UTF-8 字符 Split(s, sep) 和 SplitN(s, sep, -1) 等价；SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。

* 语法

```go
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
```

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Printf("%q\n", strings.Split("a,b,c", ""))      // ["a" "," "b" "," "c"]
    fmt.Printf("%q\n", strings.Split("a,b,c", ","))      // ["a" "b" "c"]
    fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ",")) // ["a," "b," "c"]
    fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2))  // ["a" "b,c"]
    fmt.Printf("%q\n", strings.SplitAfterN("a,b,c,d,f", ",", 2))  // ["a," "b,c,d,f"]

}
```

#### 字符串是否有某个前缀或后缀

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.HasPrefix("aaabbb","a")) // true, 判断前缀
    fmt.Println(strings.HasSuffix("aaabbb","b")) // true，判断后缀
}
```

#### 字符或子串在字符串中出现的位置

```go
// 在 s 中查找 sep 的第一次出现，返回第一次出现的索引
func Index(s, sep string) int
// 在 s 中查找字节 c 的第一次出现，返回第一次出现的索引
func IndexByte(s string, c byte) int
// chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int
// 查找字符 c 在 s 中第一次出现的位置，其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int

// 有三个对应的查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
```

#### 字符串 JOIN 操作

> 将字符串数组（或 slice）连接起来可以通过 Join 实现，函数签名如下：

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Join([]string{"name=aaa","age=18"},"&")) // 使用&拼接切片或者数组
}

// 结果
name=aaa&age=18
```

#### 字符串重复几次

> 将 s 重复 count 次，如果 count 为负数或返回值长度 len(s)\*count 超出 string 上限会导致 panic

* 语法

```go
func Repeat(s string, count int) string
```

```go
fmt.Println("ba" + strings.Repeat("na", 2))

// 输出结果
banana
```

#### 字符替换

```go
package main

import (
    "fmt"
    "strings"
    "unicode"
)


func main() {
    mapping := func(r rune) rune {
        switch {
        case r >= 'A' && r <= 'Z': // 大写字母转小写
            return r + 32
        case r >= 'a' && r <= 'z': // 小写字母不处理
            return r
        case unicode.Is(unicode.Han, r): // 汉字换行
            return '\n'
        }
        return -1 // 过滤所有非字母、汉字的字符
    }
    fmt.Println(strings.Map(mapping, "Hello你#￥%……\n（'World\n,好Hello^(&(*界gopher..."))
}

// 
hello
world
hello
gopher
```

#### 字符串子串替换

* 语法

```go
// 用 new 替换 s 中的 old，一共替换 n 个。
// 如果 n < 0，则不限制替换次数，即全部替换
func Replace(s, old, new string, n int) string  //-> old 是 s 当中需要替换的字符

// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string
```

```go
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))      // oinky oinky oink
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) // moo moo moo
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo"))  // moo moo moo
```

#### 大小写转换

* 语法

```go
func ToLower(s string) string 
func ToLowerSpecial(c unicode.SpecialCase, s string) string

func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string


func Title(s string) string    // 将 s 每个单词的首字母大写
func ToTitle(s string) string  // 将 s 的每个字母大写
func ToTitleSpecial(c unicode.SpecialCase, s string) string //  将 s 的每个字母大写，并且会将一些特殊字母转换为其对应的特殊大写字母。
```

```go
package main

import (
    "fmt"
    "strings"
)


func main() {
    fmt.Println(strings.ToLower("AAA"))     // 全部转换小写
    fmt.Println(strings.ToUpper("aaa"))     // 全部转换大写
    fmt.Println(strings.ToLower("Ā Á Ǎ À")) // 全部转换特殊字符为小写
    fmt.Println(strings.ToUpper("ā á ǎ à")) // 全部转换特殊字符为大写
    fmt.Println(strings.Title("āáǎà ōóǒò êēéěè"))    // 首字母大写
    fmt.Println(strings.ToTitle("āáǎà ōóǒò êēéěè"))  // 全部大写
    fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "āáǎà ōóǒò êēéěè")) // 全部大写包含特殊字符
}
```

#### 修剪

>

```go
// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string

// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string

// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string

// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string

// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string

// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括：'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string

// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string

// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string

// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string
```

```go
x := "!!!@@@你好,!@#$ Gophers###$$$"
fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-"))    // 你好,!@#$ Gophers
fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-"))    // 你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-"))    // !!!@@@你好,!@#$ Gophers
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))    // Hello, Gophers
fmt.Println(strings.TrimPrefix(x, "!"))    // !!@@@你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimSuffix(x, "$"))    // !!!@@@你好,!@#$ Gophers###$$

f := func(r rune) bool {
    return !unicode.Is(unicode.Han, r) // 非汉字返回 true
}
fmt.Println(strings.TrimFunc(x, f))     // 你好
fmt.Println(strings.TrimLeftFunc(x, f))     // 你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimRightFunc(x, f))     // !!!@@@你好
```

#### Replacer 类型

> 进行多个替换

```go
r := strings.NewReplacer("<", "<", ">", ">") // < 替换成 <  > 替换成 >
fmt.Println(r.Replace("This is <b>HTML</b>!"))     // This is <b>HTML</b>!
```

* 替换结果写入到文件

```go
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
```

### Go 语言结构体(struct)

> 不支持继承但支持组合的轻量级类。

* 声明结构、 访问结构、修改结构、指针地址

```go
package main

import (
    "fmt"
)

type _Class struct {
    name string
    gender string
    age int
}



func main() {

    // 声明 struct 类型变量
    var student _Class
    fmt.Println(student) // {  0} , int 类型，默认为0

    // 声明初始化结构
    _st1 := _Class{"tom","boy", 18}      // 方法一
    _st2 := _Class{name:"tom", gender:"boy", age:18}  // 方法二
    _st3 := _Class{name:"tom"}   // 方法三

    fmt.Println(_st1) // {tom boy 18}
    fmt.Println(_st2) // {tom boy 18}
    fmt.Println(_st3) // {tom  0}


    // 访问定义的结构文字
    fmt.Println(_st1.name)   // tom
    fmt.Println(_st1.gender) // boy
    fmt.Println(_st1.age)    // 18


    // 修改结构值
    _st1.name = "google"
    fmt.Println(_st1)       // {google boy 18}


    // 定义变量，并获取指向结构体的指针，既内存地址
    _get := &_Class{"a","b",1}
    fmt.Println((*_get).name) // a
    fmt.Println(_get.name) // a
}
```

#### Go 结构体比较

> 如果结构彼此相等（就其字段值而言），则运算符和方法均返回true；否则，返回false。并且，如果比较的变量属于不同的结构，则编译器将给出错误

```go
// 使用 reflect 下的 DeeplyEqual 方法

package main

import (
    "fmt"
    "reflect"
)

type _Class struct {
    name string
    gender string
    age int
}



func main() {

    _st1 := _Class{"a","女",18}
    _st2 := _Class{"a","女",18}
    _st3 := _Class{"a","女",19}

    fmt.Println(reflect.DeepEqual(_st1, _st2)) // true
    fmt.Println(reflect.DeepEqual(_st1, _st3)) // false
}
```

#### Go 嵌套结构体

* 语法

```go
type struct_name_1 struct{
  // Fields
} 
type struct_name_2 struct{
  variable_name  struct_name_1

}
```

```go
package main

import (
    "fmt"
)

type User struct {
    name string
    gender string
    age int
}

// 嵌套结构
type Group struct {
    group User
}


func main() {

    _g := Group{
            group: User{name:"tom", gender: "girl", age: 18},  // 另起一行必须要加上,号
        }

    fmt.Println(_g)             // {{tom girl 18}}
    fmt.Println(_g.group.name)  // tom， 访问结构属性
}
```

#### Go 匿名结构和字段

* 匿名结构

```go
package main

import (
    "fmt"
)


func main() {
    // 创建和初始化一次性匿名结构
    Student := struct {
        name string
        gender string
        age int
    }{
        name: "张三",
        gender: "女",
        age: 18,
    }

    fmt.Println(Student)      // {张三 女 18}


    _Student

}
```

* 匿名字段

```go
package main 

import "fmt"

//创建一个结构匿名字段 
type student struct { 
    int
    string 
    float64 
} 

// Main function 
func main() { 

    // 将值分配给匿名,学生结构的字段
    value := student{123, "Bud", 8900.23} 

    fmt.Println("入学人数 : ", value.int)       // 入学人数 :  123
    fmt.Println("学生姓名 : ", value.string)    // 学生姓名 :  Bud
    fmt.Println("套餐价格 : ", value.float64)   // 套餐价格 :  8900.23
}
```

* 不允许

```go
// 不允许创建两个或多个相同类型的字段
type student struct{
int
int
}
```

#### 结构体内嵌类型

```go
package main 

import "fmt"


type Group struct {
    group string
}

type User struct {
    uname string
    Group
}


func main() {

    var _u User
    _u.uname = "小明"
    _u.group = "技术部"

    fmt.Println(_u)  // {小明 {技术部}}
}
```

#### 函数用作结构体字段

```go
//使用匿名函数作为Go结构中的一个字段
package main 

import "fmt"

//创建结构
type Author struct { 
    name      string
    language  string
    Tarticles int
    Particles int
    Pending   func(int, int) int
} 

func main() { 

    //初始化结构字段
    result := Author{ 
        name:      "Sonia", 
        language:  "Java", 
        Tarticles: 340, 
        Particles: 259, 
        Pending: func(Ta int, Pa int) int { 
            return Ta - Pa 
        }, 
    } 

    fmt.Println("作者姓名: ", result.name) 
    fmt.Println("语言: ", result.language) 
    fmt.Println("文章总数: ", result.Tarticles) 

    fmt.Println("发表文章总数: ", result.Particles) 
    fmt.Println("待处理文章: ", result.Pending(result.Tarticles, result.Particles)) 

// 作者姓名:  Sonia
// 语言:  Java
// 文章总数:  340
// 发表文章总数:  259
// 待处理文章:  81

}
```

### Go指针

#### 指针的引用

```go
//使用匿名函数作为Go结构中的一个字段
package main 

import "fmt"



func main() { 
  b := 255
  var a *int = &b
  fmt.Println(a)  // 变量的指针地址 0xc00018c000
  fmt.Println(b)  // 变量值

}
```

#### 指针解引用

```go
//使用匿名函数作为Go结构中的一个字段
package main 

import "fmt"



func main() { 
  b := 255
  a := &b           // 引用

  fmt.Println(a)    // 打印指针地址
  fmt.Println(*a)   // 解引用

}
```

#### 传递指针给函数

```go
//使用匿名函数作为Go结构中的一个字段
package main 

import "fmt"


func change(val *int) {
    *val = 55
}

func main() { 
    a := 59
    b := &a     // 把 b 的值指向 a 的指针地址

    change(b)    // 传递指针给函数，并修改指针地址
    fmt.Println(a)

}
```

#### 函数返回指针地址

```go
package main 

import "fmt"

func res() *int {
    //局部变量, 函数内部使用简短运算符声明
    status := 200
    return &status
}


func main() {

    //调用函数
    response := res()
    fmt.Println(response)  // 0xc000014048   指针地址
    fmt.Println(*response) // 200    变量值
}
```

#### 结构体指针

```go
package main 

import "fmt"

type group struct {
    name string
}

func main() {

    test := group{"abc"}
    pts := &test
    pts.name = "xyz"
    fmt.Println(pts)    // &{xyz}
    fmt.Println(*pts)   // {xyz}
    fmt.Println(test)   // {xyz}

}
```

#### 指针比较

* \== 运算符：如果两个指针都指向同一个变量，则此运算符返回true。或如果两个指针都指向不同的变量，则返回false。
* != 运算符：如果两个指针都指向同一个变量，则此运算符返回false。或如果两个指针都指向不同的变量，则返回true。

```go
package main

import "fmt"

func main() {

    val1 := 2345
    val2 := 567

    //创建和初始化指针
    var p1 *int
    p1 = &val1
    p2 := &val2
    p3 := &val1

    // 使用 != 运算符比较指针
    res1 := &p1 != &p2
    fmt.Println("p1指针不等于p2指针吗: ", res1)

    res2 := p1 != p2
    fmt.Println("p1指针不等于p2指针吗: ", res2)

    res3 := p1 != p3
    fmt.Println("p1指针不等于p3指针吗: ", res3)

    res4 := p2 != p3
    fmt.Println("p2指针不等于p3指针吗: ", res4)

    res5 := &p3 != &p1
    fmt.Println("p3指针不等于p1指针吗: ", res5)
}
```

#### 指针容量和长度

```go
package main 

import "fmt"



func main() {
    // 创建一个容量为8个的数组
    arr := [8]int{1,2,3,4,5,6,7,8}

    // 定义循环变量类型
    // var x int

    // 创建一个容量为 5 的指针
    var p [5]*int

    // 给p分配指针地址
    for x := 0; x < len(p); x++ {
        p[x] = &arr[x]
    }

    // 打印容量为5的指针地址的值
    for x := 0; x < len(p); x++ {
        fmt.Println(*p[x])
    }

    fmt.Println("数组容量：", cap(arr))
    fmt.Println("指针容量：", cap(p))
    fmt.Println("指针容量：", cap(p))
    fmt.Println("指针长度：", len(p))
// 1
// 2
// 3
// 4
// 5
// 数组容量： 8
// 指针容量： 5
// 指针容量： 5
// 指针长度： 5
}
```

### Go 方法接收器

#### 方法简单示例

```go
package main 

import "fmt"


// 创建一个个结构体
type Student struct {
    name string
    gender string
    age int
}


// 定义Reciver()方法，作为 Student 接收器类型
func (e Student) Reciver() {
    fmt.Printf("Student of %s is %s %d", e.name, e.gender, e.age)
}


// 赋值，并调用 Student 类型的 Reciver() 方法
func main() {
    emps := Student{"Tom", "women", 18}
    emps.Reciver()  // Student of Tom is women 18
}


// 理解： 在 Student 结构体类型上创建了一个 Reciver() 方法， Reciver() 在方法的内部访问了接收器 e Student，
// 并使用接收器 e，并打印 Student 的 name、gender 和 age 这 3 个字段
```

等同于

```go
// Reciver() 方法被转化为一个函数，Student 结构体被当做参数传递给它。

package main 

import "fmt"


// 创建一个个结构体
type Student struct {
    name string
    gender string
    age int
}


// Reciver()方法被转化为一个函数，把 Student 当做参数传入。
func Reciver(e Student) {
    fmt.Printf("Student of %s is %s %d", e.name, e.gender, e.age)
}


// 赋值，并调用 Student 类型的 Reciver() 方法
func main() {
    emps := Student{"Tom", "women", 18}
    Reciver(emps)  // Student of Tom is women 18
}
```

#### 在结构上定义接收器方法

```go
package main 

import (
    "fmt"
    "math"
)

// 结构体 R1
type R1 struct {
    lenght int
    width int
}


// 结构体 R2
type R2 struct {
    radius float64
}

// R1 接收器, 使用 return 返回
func (r R1) gago() int {
    return r.lenght + r.width
}


// R2 接收器， 使用 return 返回
func (c R2) gago() float64 {
    return math.Pi * c.radius * c.radius
}


func main() {
    r := R1{lenght: 5, width: 10}
    fmt.Println(r.gago())  // 15


    c := R2{radius: 12}
    fmt.Println(c.gago())  // 452.3893421169302
}
```

#### 指针接收器与值接收器

* 指针接收器： 考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体，这是很昂贵的。在这种情况下使用指针接收器，结构体不会被拷贝，只会传递一个指针到方法内部使用。
* 值接收器： 除了指针接收器外，在其他的所有情况，都可以被使用值接收器

```go
package main 

import (
    "fmt"
)

type Student struct {
    name string
    age int
}

/* 使用值接收器 */
func (e Student) ModifyName(newName string) {
    e.name = newName
}



/* 使用指针接收器 */
func (e *Student) ModifyAge(newAge int) {
    e.age = newAge
}



func main() {
    e := Student{name: "tom", age: 20,}
    e.ModifyName("jack aaaa")  // 调用值接收器
    fmt.Println(e.name)        // tom,发现前后都是一样,值没有改变

    e.ModifyAge(50)
    fmt.Println(e.age)         // 50， 发现值已经改变，变成50
}
```

#### 匿名字段的方法

```go
package main 

import (
    "fmt"
)


type address struct {
    city string
    state string
}


func (e address) fullAddress() {
    fmt.Println(e.city , e.state)
}


// 匿名结构体
type person struct {
    firstName string
    lastName string
    address
}


func main() {
    p := person{
        firstName: "张",
        lastName: "三",
        address: address{
            city: "BeiJing",
            state: "JinShuiQu",
        },
    }

    p.fullAddress()   // BeiJing JinShuiQu
}
```

#### 在方法中使用值接收器 与 在函数中使用值参数

* 当一个函数有一个值参数，它只能接受一个值参数
* 当一个方法有一个值接收器，它可以接受值接收器和指针接收器。

```go
package main

import (
    "fmt"
)

type rectangle struct {
    length int
    width  int
}

func area(r rectangle) {
    fmt.Printf("Area Function result: %d\n", (r.length * r.width))
}

func (r rectangle) area() {
    fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}

func main() {
    r := rectangle{
        length: 10,
        width:  5,
    }
    area(r)   // Area Function result: 50, 等同如下
    r.area()  // Area Method result: 50， 等同如上

    p := &r
    /*
       compilation error, cannot use p (type *rectangle) as type rectangle
       in argument to area
    */
    //area(p)

    p.area()//通过指针调用值接收器
}
```

#### 在方法中使用指针接收器 与 在函数中使用指针参数

> 和值参数相类似，函数使用指针参数只接受指针，而使用指针接收器的方法可以使用值接收器和指针接收器。

```go
package main

import (
    "fmt"
)

type rectangle struct {
    length int
    width  int
}

// 定义了一个接受指针参数的函数 perimeter
func perimeter(r *rectangle) {
    fmt.Println("perimeter function output:", 2*(r.length+r.width))

}

// 定义了一个有一个指针接收器的方法
func (r *rectangle) perimeter() {
    fmt.Println("perimeter method output:", 2*(r.length+r.width))
}

func main() {
    r := rectangle{
        length: 10,
        width:  5,
    }
    p := &r //pointer to r
    perimeter(p)    // perimeter function output: 30
    p.perimeter()   // perimeter method output: 30

    /*
        cannot use r (type rectangle) as type *rectangle in argument to perimeter
    */
    //perimeter(r)

    r.perimeter()//使用值来调用指针接收器   perimeter method output: 30
}
```

#### 在非结构体上的方法

```go
package main

import "fmt"


// 为内置类型 int 创建一个类型别名，然后创建一个以该类型别名为接收器的方法
// 为 int 创建了一个类型别名 myInt。我们定义了一个以 myInt 为接收器的的方法 add。
type myInt int

func (a myInt) add(b myInt) myInt {
    return a + b
}

func main() {
    num1 := myInt(5)
    num2 := myInt(10)
    sum := num1.add(num2)
    fmt.Println("Sum is", sum)
}
```

### Go 接口

* 静态接口
* 动态接口
* 语法:

```go
type interface_name interface{

    //方法签名
}
```

#### 接口的声明与实现

```go
package main 

import "fmt"

// 定义接口
type MyApi interface {
    // 定义接口下的方法,返回 rune 类型切片
    Response() []rune
}

// 声明类型
type MyString string

// 接收器接受类型 MyString，并添加 Response() []rune 方法 
func (ms MyString) Response() []rune {
    // 方便操作,重新赋值返回值
    var _data []rune

    // 循环遍历类型 MyString, 也就是 ms 传值字符串
    for _, i := range ms {

        // 单引号表示字符，双引号表示字符串    
        if i == 'a' || i == 'e' {
            _data = append(_data, i)
        }
    }
    return _data
}


func main() {
    name := MyString("Sam Anderson")
    var api MyApi

    // name 是 MyString 类型，api 类型是 Myapi
    // 把 name 赋值给 api, api.Response() 调用 MyString 类型 Response()方法
    api = name
    fmt.Printf("%c", api.Response())  // [a e]
}
```

#### 接口的实际用途

> 根据公司员工的个人薪资，计算公司的总支出。为了简单起见，我们假定支出的单位都是美元。 公司员工有着不同的薪资结构,以永久工和合同工薪酬为例

```go
package main

import (  
    "fmt"
)

// 创建接口， 薪资计算器
type SalaryCalculator interface {  
    CalculateSalary() int
}

// 永久雇员工资结构体
type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

// 合同员工工资结构体
type Contract struct {  
    empId  int
    basicpay int
}

// 永久雇员的工资是基本工资和公积金的总和
func (p Permanent) CalculateSalary() int {  
    return p.basicpay + p.pf
}

// 合同工的工资是单独的基本工资
func (c Contract) CalculateSalary() int {  
    return c.basicpay
}

/*
作用说明：总费用是通过遍历SalaryCalculator切片并求和来计算的雇员个人的工资
方法说明：该方法接收一个 SalaryCalculator 接口的切片（[]SalaryCalculator）作为参数
*/
func totalExpense(s []SalaryCalculator) {  
    // 设置初始总费用为：0
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("公司每月工资总费用 $%d", expense)
}

func main() {  
    pemp1 := Permanent{1, 5000, 20}  // 第一个永久雇员的工资
    pemp2 := Permanent{2, 6000, 30}  // 第二个永久雇员的工资
    cemp1 := Contract{3, 3000}       // 合同工的工资
    employees := []SalaryCalculator{pemp1, pemp2, cemp1}
    totalExpense(employees)         // 执行上面的方法

}
```

#### 接口的内部表示

> 把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型（Concrete Type），而 value 是具体类型的值。

> Test 接口只有一个方法 Tester()，而 MyFloat 类型实现了该接口。 我们把变量 f（MyFloat 类型）赋值给了 t（Test 类型）。现在 t 的具体类型为 MyFloat，而 t 的值为 89.7。

```go
package main 
import "fmt"

// 定义接口， Test 接口只有一个方法 Tester()
type Test interface {
    Tester()
}

// 定义变量 MyFloat 为 float64
type MyFloat float64

// 定义接收器，  MyFloat 类型实现了该接口
func (m MyFloat) Tester() {
    fmt.Println(m)
}



func main() {
    var t Test          // 赋值 t 为接口
    f := MyFloat(98.1)  // 赋值 f 的变量值
    t = f               // 把 f (MyFloat) 类型的值赋值给 t ,t 的类型变成 MyFloat
    t.Tester()          // 调用接收器方法， 输出: 98.1
}
```

#### 空接口

> 没有包含方法的接口称为空接口。空接口表示为interface{}。由于空接口没有方法，因此所有类型都实现了空接口。

```go
package main 
import "fmt"


// describe(i interface{}) 函数接收空接口作为参数，因此，可以给这个函数传递任何类型
func describe(i interface{}) {
    fmt.Printf("Type = %T, value = %v\n", i, i)
}


func main() {
    s := "Hello World"
    describe(s)            // Type = string, value = Hello World


    i := 55
    describe(i)           // Type = int, value = 55


    // 匿名结构体
    strt := struct {
        name string
    }{
        name: "Hungry",
    }

    describe(strt)       // Type = struct { name string }, value = {Hungry}
}
```

#### 类型断言

> 类型断言的语法是 i.(T)

```go
package main 
import "fmt"

// 如果 i 的具体类型是 T，那么 v 赋值为 i 的底层值，而 ok 赋值为 true。
// 如果 i 的具体类型不是 T，那么 ok 赋值为 false，v 赋值为 T 类型的零值，此时程序不会报错。
// v, ok := i.(T)

func assert(i interface{}) {  
    v, ok := i.(int)
    fmt.Println(v, ok)
}

func main() {  
    var s interface{} = 56
    assert(s)    // 56 true

    var i interface{} = "Steven Paul"
    assert(i)   // 0 false
}
```

#### 类型选择（Type Switch）

> i.(T) , 而对于类型选择，类型 T 由关键字 type 代替

```go
package main 
import "fmt"

// switch i.(type) 表示一个类型选择。每个 case 语句都把 i 的具体类型和一个指定类型进行了比较。如果 case 匹配成功，会打印出相应的语句

func FindType(i interface{}) {
    switch i.(type) {
        case string:
            fmt.Printf("String %s\n", i.(string))  // String aaa
        case int:
            fmt.Printf("Int %d\n", i.(int))        // Int 111
        default:
            fmt.Printf("Unknow type\n")            // Unknow type
    }
}

func main() {
    FindType("aaa")
    FindType(111)
    FindType(111.11)
}
```

#### 类型与接口比较

> 还可以将一个类型和接口相比较。如果一个类型实现了接口，那么该类型与其实现的接口就可以互相比较。

```go
package main 
import "fmt"


// 定义一个接口
type Describer interface {
    Db()
}


// 定义结构体
type Person struct {
    name string
    age int
}


// 接收器
func (p Person) Db() {
    fmt.Printf("%s is %d years old", p.name, p.age)
}


// 定义一个方法
func findType(i interface{}) {
    switch v := i.(type) {
        case Describer:
            v.Db()
        default:
            fmt.Printf("Unknow type\n")  
    }
}


func main() {
    findType("aaaa")     // Unknow type

    // 结构体 Person 实现了 Describer 接口
    p := Person{
        name: "girl",
        age: 18,
    }
    findType(p)          // girl is 18 years old
}
```

#### 指针接受者与值接受者

```go
package main

import "fmt"

// 定义一个接口
type Describer interface {
    Db()
}

// 定义一个结构体
type Person struct {
    name string
    age int
}

// 定义一个值接收器实现接口
func (p Person) Db() {
    fmt.Printf("%s is %d years old\n", p.name, p.age)
}


type Address struct {
    state string
    country string
}

// 定义一个指针接收器实现接口
func (a *Address) Db() {
    fmt.Printf("Statee %s Country %s", a.state, a.country)
}


func main() {
    var d1 Describer  // 赋值接口变量
    p1 := Person{"tom", 29}
    d1 = p1           // 赋值值给接口
    d1.Db()           // 调用接口方法： tom is 29 years old


    p2 := Person{"Harry", 18}
    d1 = &p2          // 赋值指针给接口
    d1.Db()           // 调用接口方法： Harry is 18 years old

/*
    使用值接受者声明的方法，既可以用值来调用，也能用指针调用
*/




    var d2 Describer
    a := Address{"China","ShangHai"}
    fmt.Println(&a)  // &{China ShangHai} ,可以看出 &{China ShangHai}
    d2 = &a
    d2.Db()          // 调用接口方法： Statee China Country ShangHai


/*
    对于使用指针接受者的方法，用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值（Concrete Value）并不能取到地址，
*/
}
```

#### 实现多个接口

```go
package main

import (  
    "fmt"
)

// 薪资计算器接口
type SalaryCalculator interface {  
    DisplaySalary()
}

// 离开计算器接口
type LeaveCalculator interface {  
    CalculateLeavesLeft() int
}

type Employee struct {  
    firstName string
    lastName string
    basicPay int
    pf int
    totalLeaves int
    leavesTaken int
}

func (e Employee) DisplaySalary() {  
    fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {  
    return e.totalLeaves - e.leavesTaken
}

func main() {  
    e := Employee {
        firstName: "Naveen",
        lastName: "Ramanathan",
        basicPay: 5000,
        pf: 200,
        totalLeaves: 30,
        leavesTaken: 5,
    }
    var s SalaryCalculator = e
    s.DisplaySalary()      // Naveen Ramanathan has salary $5200

    var l LeaveCalculator = e
    fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())  // Leaves left = 25
}


// 声明了两个接口：SalaryCalculator 和 LeaveCalculator。
// 定义了结构体 Employee, 实现了 SalaryCalculator 接口的 DisplaySalary 方法, 又实现了LeaveCalculator 接口里的 CalculateLeavesLeft 方法
// 于是 Employee 就实现了 SalaryCalculator 和 LeaveCalculator 两个接口。
```

#### 接口的零值

> 接口的零值是 nil。对于值为 nil 的接口，其底层值（Underlying Value）和具体类型（Concrete Type）都为 nil。

```go
package main

import "fmt"

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    if d1 == nil {
        fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
        // d1 is nil and has type <nil> value <nil>
    }
}
```

#### 结合值和接口使用

指针类型实现接口：

```go
package main 

import (
    "fmt"
)

type Stu struct{}

func (St *Stu) Show() {
    fmt.Println("aaaaa")
}

type People interface {
    Show()
}

func main() {
    var p People = &Stu{}  // 传递结构体指针
    p.Show()  // aaaaa
}
```

值类型实现接口：

```go
package main 

import (
    "fmt"
)

type Stu struct{}

func (St Stu) Show() {
    fmt.Println("aaaaa")
}

type People interface {
    Show()
}

func main() {
    var p People = Stu{}  // 值类型
    p.Show()  // aaaaa
}
```

通过函数封装接口实现：

```go
package main 

import (
    "fmt"
)

type Stu struct{}

func (St *Stu) Show() {
    fmt.Println("aaaaa")
}

type People interface {
    Show()
}

// 实现接口
func Line() People {
    var stu *Stu
    return stu
}

func main() {
    p := Line()
    p.Show()
}
```

### Go 并发

> Go 编程语言原生支持并发。Go 使用 Go 协程（Goroutine） 和信道（Channel）来处理并发

#### Go 协程

* 启动一个新的协程时，协程的调用会立即返回。与函数不同，程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后，程序控制会立即返回到代码的下一行，忽略该协程的任何返回值。
* 如果希望运行其他 Go 协程，Go 主协程必须继续运行着。如果 Go 主协程终止，则程序终止，于是其他 Go 协程也不会继续运行。

```go
package main 

import (
    "fmt"
    "time"
)


func hello() {
    fmt.Println("Helo world")
}

func main() {
    go hello()
    // time.Sleep(1 * time.Second) , 如果想输出 hello 函数方法,使用休眠，以便等待其他协程执行完毕
    fmt.Println("main")  // main
}

// 调用了 go hello() 之后，程序控制没有等待 hello 协程结束，立即返回到了代码下一行，打印 main function。接着由于没有其他可执行的代码，Go 主协程终止，于是 hello 协程就没有机会运行了。
```

#### 启动多个 Go 协程(并发)

> 启动了两个 Go 协程。现在，这两个协程并发地运行。numbers 协程首先休眠 250 微秒，接着打印 1，然后再次休眠，打印 2，依此类推，一直到打印 5 结束。alphabete 协程同样打印从 a 到 e 的字母，并且每次有 400 微秒的休眠时间。 Go 主协程启动了 numbers 和 alphabete 两个 Go 协程，休眠了 3000 微秒后终止程序。

```go
package main

import (
    "fmt"
    "time"
)


func numbers() {
    for i := 0; i <= 5;i ++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d",i)
    }
}

func alphabets() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}

func main() {  
    go numbers()
    go alphabets()
    time.Sleep(3000 * time.Millisecond)
    fmt.Println("main terminated")
}

// 0a 12b 3c 45d e main terminated
```

### 信道(channel)

#### 无缓冲信道

> Go 协程之间的管道，通过使用信道，数据也可以从一端发送，在另一端接收。实现 Go 协程间的通信。

* 信道的声明

> 所有信道都关联了一个类型。信道只能运输这种类型的数据，而运输其他类型的数据都是非法的。

```go
package main 

import (
    "fmt"
)


func main() {
    var a chan int      // 定义信道 a， 类型 int,默认零值
    if a == nil {
        a := make(chan int)  // 用 make 来定义信道
        fmt.Printf("Type of a is %T", a)  // Type of a is chan int
    }

}
```

* 信道进行发送和接收

> 默认是阻塞

```go
data := <- a   // 读取信道 a
a <- data      // 写入信道 a
```

```go
package main 

import (
    "fmt"
)

// Go 主协程发生了阻塞，等待信道 done 发送的数据。该信道作为参数传递给了协程 hello，
// hello 打印出 Hello world , done 写入数据。
// 当完成写入时，Go 主协程会通过信道 done 接收数据，于是它解除阻塞状态，打印出文本 main


/*
创建了一个 bool 类型的信道 done，并把 done 作为参数传递给了 hello 协程
通过信道 done 接收数据, 这一行代码发生了阻塞，
除非有协程向 done 写入数据，否则程序不会跳到下一行代码
于是，这就不需要用以前的 time.Sleep 来阻止 Go 主协程退出了。
*/ 

func hello(done chan bool) {
    fmt.Println("Hello world")
    done <- true     // done 接收写入数据
}


func main() {
    done := make(chan bool)
    go hello(done)
    <- done              // 读取数据
    fmt.Println("main")
}

/*
    Hello world
    main
*/ 
```

> 在一个单独的 Go 协程计算平方和，而在另一个协程计算立方和，最后在 Go 主协程把平方和与立方和相加。

```go
package main

import (  
    "fmt"
)

func calcSquares(number int, squareop chan int) {  
    sum := 0
    for number != 0 {
        digit := number % 10
        sum += digit * digit
        number /= 10
    }
    squareop <- sum
}

func calcCubes(number int, cubeop chan int) {  
    sum := 0 
    for number != 0 {
        digit := number % 10
        sum += digit * digit * digit
        number /= 10
    }
    cubeop <- sum
} 

func main() {  
    number := 589
    sqrch := make(chan int)
    cubech := make(chan int)
    go calcSquares(number, sqrch)
    go calcCubes(number, cubech)
    squares, cubes := <-sqrch, <-cubech
    fmt.Println("Final output", squares + cubes)
}
```

**死锁**

> 当 Go 协程给一个信道发送数据时，照理说会有其他 Go 协程来接收数据。如果没有的话，程序就会在运行时触发 panic，形成死锁。

```go
package main

func main() {  
    ch := make(chan int)
    ch <- 5
}
```

```go
// 错误信息
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:  
main.main()  
    /tmp/sandbox249677995/main.go:6 +0x80
```

**单向信道**

> 信道转换,把一个双向信道转换成唯送信道或者唯收（Receive Only）信道都是行得通的

```go
package main

import "fmt"

func sendData(sendch chan<- int) {  
    sendch <- 10
}

func main() {  
    cha1 := make(chan int)
    go sendData(cha1)
    fmt.Println(<-cha1)
}
```

> 我们创建了一个双向信道 cha1。在第 11 行 cha1 作为参数传递给了 sendData 协程。在第 5 行，函数 sendData 里的参数 sendch chan<- int 把 cha1 转换为一个唯送信道。于是该信道在 sendData 协程里是一个唯送信道，而在 Go 主协程里是一个双向信道。该程序最终打印输出 10。

**关闭信道和使用 for range 遍历信道**

* 成功接收信道所发送的数据，那么 ok 等于 true。而如果 ok 等于 false

> v, ok := <- ch

```go
package main

import (
    "fmt"
)


func producer(chnl chan int) {
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}

func main() {
    ch := make(chan int)
    go producer(ch)
    for {
        v, ok := <- ch
        if ok == false {
            break
        }
        fmt.Println("Received ", v, ok)
    }
}
```

> producer 协程会从 0 到 9 写入信道 chn1，然后关闭该信道。 主函数有一个无限的 for 循环，使用变量 ok 检查信道是否已经关闭。如果 ok 等于 false，说明信道已经关闭，于是退出 for 循环。如果 ok 等于 true，会打印出接收到的值和 ok 的值。

```go
Received  0 true
Received  1 true
Received  2 true
Received  3 true
Received  4 true
Received  5 true
Received  6 true
Received  7 true
Received  8 true
Received  9 true
```

* 优化

for range 循环用于在一个信道关闭之前，从信道接收数据。

```go
package main 

import (
    "fmt"
)

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}


func main() {
    ch := make(chan int)
    go producer(ch)

    // for range 循环从信道 ch 接收数据，直到该信道关闭。一旦关闭了 ch，循环会自动结束。该程序会输出：
    for v := range ch {
        fmt.Println("Received ",v)
    }
}
```

```go
Received  0
Received  1
Received  2
Received  3
Received  4
Received  5
Received  6
Received  7
Received  8
Received  9
```

#### 缓冲信道

> 创建一个有缓冲（Buffer）的信道。只在缓冲已满的情况，才会阻塞向缓冲信道（Buffered Channel）发送数据。同样，只有在缓冲为空的时候，才会阻塞从缓冲信道接收数据。

* 语法

```go
// 信道有缓冲 capacity 应该大于 0。无缓冲信道的容量默认为 
ch := make(chan type, capacity)
```

```go
package main

import (
    "fmt"
    "time"
)

func write(ch chan int) {
    for i := 0; i < 6; i++ {
        ch <- i
        fmt.Println("Successfully wrote", i, "to ch")
    }
    close(ch)
}


func main() {
    // 缓冲值是 2
    ch := make(chan int, 2)
    go write(ch)
    // <- ch
    time.Sleep(1 * time.Second)
    for v := range ch {
        fmt.Println("read value", v,"from ch")
        time.Sleep(1 * time.Second)
    }
}
```

```go
Successfully wrote 0 to ch
Successfully wrote 1 to ch
read value 0 from ch
Successfully wrote 2 to ch
read value 1 from ch
Successfully wrote 3 to ch
read value 2 from ch
Successfully wrote 4 to ch
read value 3 from ch
Successfully wrote 5 to ch
read value 4 from ch
read value 5 from ch
```

**死锁**

```go
package main

import (  
    "fmt"
)

func main() {  
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    ch <- "steve"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}
```

> 在上面程序里，我们向容量为 2 的缓冲信道写入 3 个字符串。当在程序控制到达第 3 次写入时，由于它超出了信道的容量，因此这次写入发生了阻塞。现在想要这次写操作能够进行下去，必须要有其它协程来读取这个信道的数据。但在本例中，并没有并发协程来读取这个信道，因此这里会发生死锁（deadlock）。程序会在运行时触发 panic

```go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:  
main.main()  
    /tmp/sandbox274756028/main.go:11 +0x100
```

**长度 vs 容量**

* 缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。
* 缓冲信道的长度是指信道中当前排队的元素个数。

```go
package main

import (  
    "fmt"
)

func main() {  
    ch := make(chan string, 3)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println("capacity is", cap(ch))     // 信道容量  capacity is 3
    fmt.Println("length is", len(ch))       // 信道长度  length is 2
    fmt.Println("read value", <-ch)         // 打印内容  read value naveen
    fmt.Println("new length is", len(ch))   // 使用过后的长度 new length is 1
}
```

#### WaitGroup 工作池

* WaitGroup 用于等待一批 Go 协程执行结束。程序控制会一直阻塞，直到这些协程全部执行完毕

> 假设我们有 3 个并发执行的 Go 协程（由 Go 主协程生成）。Go 主协程需要等待这 3 个协程执行结束后，才会终止。这就可以用 WaitGroup 来实现。

```go
package main

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


func process(i int, wg *sync.WaitGroup) {
    fmt.Println("started Goroutine ", i)
    time.Sleep(2 * time.Second)
    fmt.Printf("Goroutine %d ended\n", i)
    wg.Done()        // 减少计数器，调用 WaitGroup 的 Done() 方法
}

func main() {
    no := 3
    var wg sync.WaitGroup         // 创建 WaitGroup 类型变量，初始值为 0
    for i := 0; i < no; i++ {
        wg.Add(1)                 // 调用 WaitGroup 的 Add 并传递一个 int 时.,WaitGroup 的计数器会加上 Add 的传参
        go process(i, &wg)
    }

    wg.Wait()    // Wait() 方法会阻塞调用它的 Go 协程，直到计数器变为 0 后才会停止阻塞
    fmt.Println("All go routines finished executing")

}
```

```go
// 由于 Go 协程的执行顺序不一定
started Goroutine  2
started Goroutine  1
started Goroutine  0
Goroutine 0 ended
Goroutine 1 ended
Goroutine 2 ended
All go routines finished executing
```

**工作池缓冲信道实现**

* 1、创建一个 Go 携程池，监听一个等待作业分配的输入型缓冲信道
* 2、将作业添加到该输入型缓冲信道中
* 3、作业完成后，再将结果写入一个输出型缓冲信道
* 4、从输出型缓冲信道读取并打印结果

```go
package main

import (
    "fmt"
    "sync"
    "time"
    "math/rand"
)

// 第一步： 创建结构体，表示作业和结果
type Job struct {
    id int
    randomno int   // 用于计算其每位数之和
}

type Result struct {
    job Job         // 表示所对应的作业
    sumofdigits int // 表示计算的结果（每位数字之和）
}


// 第二步：创建用于接受作业和写入结果的缓冲信道
var jobs = make(chan Job, 10)
var results = make(chan Result, 10)

// digits 函数的任务实际上就是计算整数的每一位之和，最后返回该结果
func digits(number int) int {
    // 默认结果未 0
    sum := 0
    // 赋值变量
    no := number
    for no != 0 {
        digits := no % 10
        sum += digits
        no /= 10
    }
    // 为了模拟出 digits 在计算过程中花费了一段时间，我们在函数内添加了两秒的休眠时间
    time.Sleep(1 * time.Second)
    return sum
}


// 然后，我们写一个创建工作协程的函数
/*
创建一个工作者 worker, 读取 jobs 信道的数据，根据当前的 job 和 digits 的返回值，
创建了一个 Result 结构体变量，然后将结果写入 results 缓冲信道。
worker 函数接收一个 WaitGroup 类型的 wg 作为参数，当所有的 jobs 完成时候，调用 Done() 方法
*/ 
func worker(wg *sync.WaitGroup) {
    for job := range jobs {
        output := Result{job, digits(job.randomno)}
        results <- output
    }
    wg.Done()
}

// creatWorkerPool 函数创建了一个 Go 协程的工作池
/*
函数的参数是需要创建的工作协程的数量
在创建 Go 协程之前，它调用了 wg.Add(1)方法，于是 WaitGroup 计数器递增，
接下来，我们创建工作协程，并向 worker 函数传递 wg 地址。
创建了需要的工作协程后，函数调用了 wg.Wait()，等待所有的 Go 协程执行完毕。
所有协程完成执行之后，函数会关闭 results 信道。
因为所有协程都已经执行完毕，于是不再需要向 results 信道写入数据
*/ 
func createWorkerPool(noOfWorkers int) {
    var wg sync.WaitGroup
    for i := 0; i < noOfWorkers; i++ {
        wg.Add(1)
        go worker(&wg)
    }
    wg.Wait()
    close(results)
}


// 现在我们已经有了工作池，继续编写一个函数，把作业分配给工作者
/*
allocate 函数接收所需创建的作业数量作为输入参数，生成了最大值为 9 的伪随机数，并使用该随机数创建了 Job 结构体变量。
这个函数把 for 循环的计数器 i 作为 id，最后把创建的结构体变量写入 jobs 信道。当写入所有的 job 时，它关闭了 jobs 信道。
*/ 
func allocate(noOfJobs  int) {
    for i := 0; i < noOfJobs ; i++ {
        randomno := rand.Intn(9)
        job := Job{i, randomno}
        jobs <- job
    }
    close(jobs)
}


// 创建一个读取 results 信道和打印输出的函数。
/*
result 函数读取 results 信道，并打印出 job 的 id、输入的随机数、该随机数的每位数之和。
result 函数也接受 done 信道作为参数，当打印所有结果时，done 会被写入 true。
*/ 
func result(done chan bool) {  
    for result := range results {
        fmt.Printf("Job id %d, input random no %d , sum of digits %d\n", result.job.id, result.job.randomno, result.sumofdigits)
    }
    done <- true
}



func main() {  
    startTime := time.Now()
    noOfJobs := 10
    go allocate(noOfJobs)
    done := make(chan bool)
    go result(done)
    noOfWorkers := 5
    createWorkerPool(noOfWorkers)
    <-done
    endTime := time.Now()
    diff := endTime.Sub(startTime)
    fmt.Println("total time taken ", diff.Seconds(), "seconds")
}
```

```go
Job id 4, input random no 4 , sum of digits 4
Job id 1, input random no 6 , sum of digits 6
Job id 0, input random no 5 , sum of digits 5
Job id 2, input random no 2 , sum of digits 2
Job id 3, input random no 2 , sum of digits 2
Job id 5, input random no 6 , sum of digits 6
Job id 7, input random no 8 , sum of digits 8
Job id 6, input random no 7 , sum of digits 7
Job id 8, input random no 4 , sum of digits 4
Job id 9, input random no 6 , sum of digits 6
total time taken  2.000395302 seconds
```

> 随着工作协程数量增加，完成作业的总时间会减少。你们可以练习一下：在 main 函数里修改 noOfJobs 和 noOfWorkers 的值，并试着去分析一下结果。

### select(信道操作)

> select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞，直到发送/接收操作准备就绪。如果有多个信道操作准备完毕，select 会随机地选取其中之一执行。该语法与 switch 类似，所不同的是，这里的每个 case 语句都是信道操作

#### 默认情况

> 在没有 case 准备就绪时，可以执行 select 语句中的默认情况（Default Case）。这通常用于防止 select 语句一直阻塞。

```go
package main

import (
    "fmt"
    "time"
)


func process(ch chan string) {
    time.Sleep(10500 * time.Millisecond)
    ch <- "process successfull"
} 

func main() {
    ch := make(chan string)
    go process(ch)
    for {
        time.Sleep(1000 * time.Millisecond)
        select {
            case v := <- ch:
                fmt.Println("received value:", v)
                return
            default:
                fmt.Println("No have value")
        }
    }
}

/*
并发地调用了 process 协程之后，主协程启动了一个无限循环。
这个无限循环在每一次迭代开始时，都会先休眠 1000 毫秒（1 秒），然后执行一个 select 操作。
在最开始的 10500 毫秒中，由于 process 协程在 10500 毫秒后才会向 ch 信道写入数据，因此 select 语句的第一个 case（即 case v := <-ch:）并未就绪。
所以在这期间，程序会执行默认情况，该程序会打印 10 次 no value received。

在 10.5 秒之后，process 协程会在第 10 行向 ch 写入 process successful。
现在，就可以执行 select 语句的第一个 case 了，程序会打印 received value: process successful，然后程序终止。该程序会输出：
*/ 
```

#### 死锁与默认情况

```go
package main

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    }
}
```

```go
// 创建了一个信道 ch。我们在 select 内部（第 6 行），试图读取信道 ch。由于没有 Go 协程向该信道写入数据，因此 select 语句会一直阻塞，导致死锁。该程序会触发运行时 panic，报错信息如下：

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox416567824/main.go:6 +0x80
```

> 如果存在默认情况，就不会发生死锁，因为在没有其他 case 准备就绪时，会执行默认情况。我们用默认情况重写后

```go
package main

import "fmt"

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    default:
        fmt.Println("default case executed")
    }
}
```

> 如果 select 只含有值为 nil 的信道，也同样会执行默认情况。

```go
package main

import "fmt"

func main() {  
    var ch chan string
    select {
    case v := <-ch:
        fmt.Println("received value", v)
    default:
        fmt.Println("default case executed")

    }
}

/*
ch 等于 nil，而我们试图在 select 中读取 ch（第 8 行）。如果没有默认情况，select 会一直阻塞，导致死锁。由于我们在 select 内部加入了默认情况，程序会执行它
 */ 
```

#### 随机选取

> 当 select 由多个 case 准备就绪时，将会随机地选取其中之一去执行。

```go
package main

import (  
    "fmt"
    "time"
)

func server1(ch chan string) {  
    ch <- "from server1"
}
func server2(ch chan string) {  
    ch <- "from server2"

}
func main() {  
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    time.Sleep(1 * time.Second)
    select {
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}
```

#### 空 select

> 除非有 case 执行，select 语句就会一直阻塞着。在这里，select 语句没有任何 case，因此它会一直阻塞，导致死锁。

```go
ackage main

func main() {  
    select {}
}
```

### Mutex(加锁)

* 临界区： 当程序并发地运行时，多个 Go 协程不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区
* Mutex 用于提供一种加锁机制（Locking Mechanism），可确保在某时刻只有一个协程在临界区运行，以防止出现竞态条件。
* 方法
  * mutex.Lock()
  * mutex.Unlock()

```go
mutex.Lock()  
x = x + 1  
mutex.Unlock()

// 如果有一个 Go 协程已经持有了锁（Lock），当其他协程试图获得该锁时，这些协程会被阻塞，直到 Mutex 解除锁定为止
```

#### 使用 Mutex

> 我们创建了 1000 个 Go 协程。如果每个协程对 x 加 1，最终 x 期望的值应该是 1000, 使用 Mutex，修复竞态条件的问题。

```go
package main

import (
    "fmt"
    "sync"
)

var x = 0

func increment(wg *sync.WaitGroup, m *sync.Mutex) {
    m.Lock()
    x = x + 1
    m.Unlock()
    wg.Done()
}

/*
我们修改了 increment 函数，将增加 x 的代码（x = x + 1）放置在 m.Lock() 和 m.Unlock()之间。现在这段代码不存在竞态条件了，因为任何时刻都只允许一个协程执行这段代码。
 */ 

func main() {
    var w sync.WaitGroup
    var m sync.Mutex // 定义加锁
    for i := 0; i < 1000; i++ {
        w.Add(1)
        go increment(&w, &m)
    }
    w.Wait()
    fmt.Println("Final value of x", x)  // Final value of x 1000

}
```

#### 使用信道处理竞态条件

* 信道来处理竞态条件

```go
package main 

import (
    "fmt"
    "sync"
)


var x = 0

func increment(wg *sync.WaitGroup,ch chan bool) {
    ch <- true
    x = x + 1
    <- ch
    wg.Done()
}

func main() {
    var w sync.WaitGroup
    ch := make(chan bool, 1)
    for i := 0; i < 10; i++{
        w.Add(1)
        go increment(&w,ch)
    }
    w.Wait()
    fmt.Println("Finshed value of x", x) // Finshed value of x 10
}
```

#### Mutex vs 信道

* 当 Go 协程需要与其他协程通信时，可以使用信道。
* 而当只允许一个协程访问临界区时，可以使用 Mutex。

63 + 49 + 39.60

### Go实现面向对象

* 使用结构体，而非类

> Go 不支持类，而是提供了结构体。结构体中可以添加方法。这样可以将数据和操作数据的方法绑定在一起，实现与类相似的效果。

```bash
opp/
├── employee
│   └── employee.go
└── main.go
```

employee.go

```go
package employee

import (  
    "fmt"
)

type employee struct {  
    firstName   string
    lastName    string
    totalLeaves int
    leavesTaken int
}

/* 
Go 不支持类，但结构体能够很好地取代类，而以 New(parameters) 签名的方法可以替代构造器。
提供了一个可引用的 New 函数，该函数接收必要的参数，返回一个新创建的 employee 结构体变量。
*/
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {  
    e := employee {firstName, lastName, totalLeave, leavesTaken}
    return e
}

func (e employee) LeavesRemaining() {  
    fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}
```

main.go

```go
package main  

import "oop/employee"

func main() {  
    e := employee.New("Sam", "Adolf", 30, 20)
    e.LeavesRemaining()    // Sam Adolf has 10 leaves remaining
}
```

### Go 组合取代继承

> Go 不支持继承，但它支持组合（Composition）。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子：一辆汽车由车轮、引擎和其他各种部件组合在一起。

#### 通过嵌套结构体进行组合

* 例如: 组合的典型例子就是博客帖子。每一个博客的帖子都有标题、内容和作者信息

```go
package main

import (
    "fmt"
)

/*
1、创建 author 结构体， author 字段有 firename、lastname、bio
2、添加了 fullName() 方法，其中 author 作为接受者类型，该方法返回作者的全名
*/ 

type author struct {
    firstName string
    lastName string
    bio string
}


func (a author) fullName() string {
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}


/*
1、post 结构体的字段有 title、content, 嵌套匿名字段 author
2、post 可以访问 author 结构体的所有字段和方法
3、post 结构体添加了 details() 方法，用于打印标题、内容和作者的全名与简介
*/ 


type post struct {
    title string
    content string
    author           // 嵌套匿名字段 author
}

func (p post) details() {
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.fullName())
    fmt.Println("Bio: ", p.bio)
}



func main() {
    author1 := author{"张", "三", "Golang"}
    post1 := post{"标题", "内容", author1}
    post1.details()
}
```

```go
Title:  标题
Content:  内容
Author:  张 三
Bio:  Golang
```

#### 结构体切片的嵌套

> 进一步处理这个示例，使用博客帖子的切片来创建一个网站

```go
package main

import (
    "fmt"
)

/*
1、创建 author 结构体， author 字段有 firename、lastname、bio
2、添加了 fullName() 方法，其中 author 作为接受者类型，该方法返回作者的全名
*/ 

type author struct {
    firstName string
    lastName string
    bio string
}


func (a author) fullName() string {
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}


/*
1、post 结构体的字段有 title、content, 嵌套匿名字段 author
2、post 可以访问 author 结构体的所有字段和方法
3、post 结构体添加了 details() 方法，用于打印标题、内容和作者的全名与简介
*/ 


type post struct {
    title string
    content string
    author           // 嵌套匿名字段 author
}

func (p post) details() {
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.fullName())
    fmt.Println("Bio: ", p.bio)
}


/*
结构体切片的嵌套，
使用博客帖子的切片来创建一个网站
*/ 

type website struct {
    posts []post      // 结构体不能嵌套一个匿名切片,需要指定一个字段名来嵌套
}

func (w website) contents() {
    fmt.Println("Contents of Website\n")
    for _, v := range w.posts {
        v.details()
        fmt.Println()
    }
}


func main() {
    author1 := author{"张", "三", "Golang"}
    post1 := post{
        "Inheritance in Go",
        "Go supports composition instead of inheritance",
        author1,
    }
    post2 := post{
        "Struct instead of Classes in Go",
        "Go does not support classes but methods can be added to structs",
        author1,
    }
    post3 := post{
        "Concurrency",
        "Go is a concurrent language and not a parallel one",
        author1,
    }

    w := website{
        posts: []post{post1,post2, post3},
    }
    w.contents()
}
```

```go
Contents of Website

Title:  Inheritance in Go
Content:  Go supports composition instead of inheritance
Author:  张 三
Bio:  Golang

Title:  Struct instead of Classes in Go
Content:  Go does not support classes but methods can be added to structs
Author:  张 三
Bio:  Golang

Title:  Concurrency
Content:  Go is a concurrent language and not a parallel one
Author:  张 三
Bio:  Golang
```

63 + 49 + 39.60

### 多态

> 所有实现了接口的类型，都可以把它的值保存在一个接口类型的变量中。在 Go 中，我们使用接口的这种特性来实现多态。

#### 使用接口实现多态

> 假设某个虚构的组织所获得的收入来源于两个项目：fixed billing 和 time and material。该组织的净收益等于这两个项目的收入总和

```go
package main 

import "fmt"


// 定义一个接口, 接口里有两个方法
type Inome interface {
    calculate() int     // 计算并返回项目的收入
    source() string     // 返回项目名称
}


// 项目类型一
type FixedBilling struct {  
    projectName string   // 项目名称
    biddedAmount int     // 该项目投标的金额
}


// 项目类型二
type TimeAndMaterial struct {  
    projectName string    // 项目名称
    noOfHours  int        
    hourlyRate int
}


// 下一步我们给这些结构体类型定义方法，计算并返回实际收入和项目名称。
// 由于 FixedBilling 和 TimeAndMaterial 两个结构体都定义了 Income 接口的两个方法：calculate() 和 source()，
// 因此这两个结构体都实现了 Income 接口。
func (fb FixedBilling) calculate() int {
    return fb.biddedAmount       // 返回FixedBilling实际收入
}

func (fb FixedBilling) source() string {
    return fb.projectName        // 返回FixedBilling项目名称。
}


func (tm TimeAndMaterial) calculate() int {  
    return tm.noOfHours * tm.hourlyRate // 返回 TimeAndMaterial 实际收入,收入等于 noOfHours 和 hourlyRate 的乘积
}


func (tm TimeAndMaterial) source() string {  
    return tm.projectName    // 返回 TimeAndMaterial 项目名称。
}


/*
声明一个 calculateNetIncome 函数，用来计算并打印总收入。
接收一个 Income 接口类型的切片作为参数。该函数会遍历这个接口切片，并依个调用 calculate() 方法，计算出总收入。该函数同样也会通过调用 source() 显示收入来源。根据 Income 接口的具体类型，程序会调用不同的 calculate() 和 source() 方法。于是，我们在 calculateNetIncome 函数中就实现了多态。
 */
func calculateNetIncome(ic []Inome) {
    var netincome int = 0
    for _, i := range ic {
        fmt.Printf("Income From %s = $%d\n", i.source(), i.calculate())
        netincome += i.calculate()
    }
    fmt.Printf("Net income of organisation = $%d", netincome)
}


/*
main 函数中，我们创建了三个项目，有两个是 FixedBilling 类型，一个是 TimeAndMaterial 类型。接着我们创建了一个 Income 类型的切片，存放了这三个项目。由于这三个项目都实现了 Interface 接口，因此可以把这三个项目放入 Income 切片。最后我们将该切片作为参数，调用了 calculateNetIncome 函数，显示了项目不同的收益和收入来源。

如果在该组织以后增加了新的收入来源，calculateNetIncome 无需修改一行代码，就可以正确地计算总收入了。:smile:

*/ 

func main() {
    project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    incomeStreams := []Inome{project1, project2, project3}  // 定义接口传值切片
    calculateNetIncome(incomeStreams)
}
```

```go
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organisation = $19000
```

#### 新增收益流(动态添加了新项目)

```go
// 上面代码添加
type Advertisement struct {  
    adName     string
    CPC        int
    noOfClicks int
}

func (a Advertisement) calculate() int {  
    return a.CPC * a.noOfClicks
}

func (a Advertisement) source() string {  
    return a.adName
}


// 上面main代码修改如下 
func main() {  
    project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}
    popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}
    incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}
    calculateNetIncome(incomeStreams)
}
```

```go
Income From Project 1 = $5000  
Income From Project 2 = $10000  
Income From Project 3 = $4000  
Income From Banner Ad = $1000  
Income From Popup Ad = $3750  
Net income of organisation = $23750
```

> 尽管我们新增了收益流，但却完全没有修改 calculateNetIncome 函数。这就是多态带来的好处。由于新的 Advertisement 同样实现了 Income 接口，所以我们能够向 incomeStreams 切片添加 Advertisement。calculateNetIncome 无需修改，因为它能够调用 Advertisement 类型的 calculate() 和 source() 方法。

### 错误处理

#### 示例

```go
package main

import (
    "fmt"
    "os"
)

/*
在处理错误时，通常都是将返回的错误与 nil 比较
nil 值表示了没有错误发生，而非 nil 值表示出现了错误
*/ 
func main() {
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)  // open /test.txt: no such file or directory
        return
    }
    fmt.Println(f.Name())
}
```

#### 错误类型的表示

```go
type error interface {  
    Error() string
}
```

#### 获取错误详细信息的各种方法

* 1、断言底层结构体类型，使用结构体字段获取更多信息

```go
package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")

    // 使用了类型断言（Type Assertion）来获取 error 接口的底层值
    if err, ok := err.(*os.PathError); ok {
        fmt.Println("File at path", err.Path, "failed to open")
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
```

* 2、断言底层结构体类型，调用方法获取更多信息

> 编写一个程序，断言 \*DNSError 类型，并调用这些方法来确定该错误是临时性错误，还是由超时导致的。

```go
package main

import (  
    "fmt"
    "net"
)

func main() {  
    addr, err := net.LookupHost("golangbot123.com")
    if err, ok := err.(*net.DNSError); ok {
        if err.Timeout() {
            fmt.Println("operation timed out")
        } else if err.Temporary() {
            fmt.Println("temporary error")
        } else {
            fmt.Println("generic error: ", err)
        }
        return
    }
    fmt.Println(addr)
}
```

* 3、直接比较

> 查询了模式为 \[ 的文件，然而这个模式写的不正确。我们检查了该错误是否为 nil。为了获取该错误的更多信息，将 error 直接与 filepath.ErrBadPattern 相比较。如果该条件满足，那么该错误就是由模式错误导致的

```go
package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, error := filepath.Glob("[")
    if error != nil && error == filepath.ErrBadPattern {
        fmt.Println(error)
        return
    }
    fmt.Println("matched files", files)
}
```

* 4、不可忽略错误

```go
package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    // 通过使用 _ 空白标识符，我忽略了 Glob 函数返回的错误
    files, _ := filepath.Glob("[")
    fmt.Println("matched files", files)  // matched files []
}
```

#### 使用 New 函数创建自定义错误

```go
package main 

import (
    "errors"
    "fmt"
    "math"
)

/* 
定义方法，接收一个 float64 类型参数，返回 float64 和 error
New 函数自定义返回的错误信息
*/ 
func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, errors.New("radius is less than zero") 
    }

    return math.Pi * radius * radius, nil
}

func main() {
    radius := -20.0
    area, err := circleArea(radius)
    // 检查错误是否等于 nil。如果不是 nil，我们会打印出错误并返回，否则我们会打印出圆的面积
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}
```

#### 使用 Errorf 给错误添加更多信息

> 上面错误是固定的，如果我们想给错误信息添加额外的信息，比如把半径值打印出来

* Errorf 函数会根据格式说明符，规定错误的格式，并返回一个符合该错误的字符串。

```go
package main

import (  
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
    }
    return math.Pi * radius * radius, nil
}

func main() {  
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}
```

#### 使用结构体类型和字段提供错误的更多信息

```go
package main

import (  
    "fmt"
    "math"
)

type areaError struct {  
    err    string
    radius float64
}

func (e *areaError) Error() string {  
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}

func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, &areaError{"radius is negative", radius}
    }
    return math.Pi * radius * radius, nil
}

func main() {  
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            fmt.Printf("Radius %0.2f is less than zero", err.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of rectangle1 %0.2f", area)
}
```

#### 使用结构体类型的方法来提供错误的更多信息

> 编写一个计算矩形面积的程序。如果长或宽小于零，程序就会打印出错误。

```go
// 第一步就是创建一个表示错误的结构体。




// 有了错误类型，我们来实现 error 接口，并给该错误类型添加两个方法，使它提供了更多的错误信息


package main

import "fmt"

type areaError struct {  
    err    string  //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}

// Error() string 方法中返回了关于错误的描述
func (e *areaError) Error() string {  
    return e.err
}

// 当 length 小于零时，lengthNegative() bool 方法返回 true
func (e *areaError) lengthNegative() bool {  
    return e.length < 0
}

// 当 width 小于零时，widthNegative() bool 方法返回 true
func (e *areaError) widthNegative() bool {  
    return e.width < 0
}


// rectArea 函数检查了长或宽是否小于零，如果小于零，rectArea 会返回一个错误信息，否则 rectArea 会返回矩形的面积和一个值为 nil 的错误。
func rectArea(length, width float64) (float64, error) {  
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}



/*

我们检查了错误是否为 nil。如果错误值不是 nil，我们会在下一行断言 *areaError 类型。
然后，我们使用 lengthNegative() 和 widthNegative() 方法，检查错误的原因是长度小于零还是宽度小于零。
这样我们就使用了错误结构体类型的方法，来提供更多的错误信息。

如果没有错误发生，就会打印矩形的面积。
 */ 
func main() {  
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}
```

```go
error: length -5.00 is less than zero  
error: width -9.00 is less than zero
```

### panic|recover

* 有些情况，当程序发生异常时，无法继续运行。在这种情况下，我们会使用 panic 来终止程序

> 可以认为 panic 和 recover 与其他语言中的 try-catch-finally 语句类似

#### 什么时候应该使用 panic

> 发生了一个不能恢复的错误，此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下，就应该使用 panic，因为如果不能绑定端口，啥也做不了。

panic 有两个合理的用例。

* 1、发生了一个不能恢复的错误，此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下，就应该使用 panic，因为如果不能绑定端口，啥也做不了。
* 2、发生了一个编程上的错误。 假如我们有一个接收指针参数的方法，而其他人使用 nil 作为参数调用了它。在这种情况下，我们可以使用 panic，因为这是一个编程错误：用 nil 参数调用了一个只能接收合法指针的方法。

#### panic 示例

```go
package main

import (  
    "fmt"
)

func fullName(firstName *string, lastName *string) {  
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

```go
// 当出现了 panic 时，程序就会终止运行，打印出传入 panic 的参数
panic: runtime error: last name cannot be nil 


// 接着打印出堆栈跟踪
goroutine 1 [running]:  
main.fullName(0x1040c128, 0x0)  
    /tmp/sandbox135038844/main.go:12 +0x120
main.main()  
    /tmp/sandbox135038844/main.go:20 +0x80
```

#### 发生 panic 时的 defer

> 当函数发生 panic 时，它会终止运行，在执行完所有的延迟函数后，程序控制返回到该函数的调用方。这样的过程会一直持续下去，直到当前协程的所有函数都返回退出，然后程序会打印出 panic 信息，接着打印出堆栈跟踪，最后程序终止。

* 发生 panic 时，首先执行了延迟函数，接着控制返回到函数调用方，调用方的延迟函数继续运行，直到到达顶层调用函数。

```go
package main

import (  
    "fmt"
)

func fullName(firstName *string, lastName *string) {  
    defer fmt.Println("deferred call in fullName")
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

```go
This program prints,

deferred call in fullName  
deferred call in main  
panic: runtime error: last name cannot be nil

goroutine 1 [running]:  
main.fullName(0x1042bf90, 0x0)  
    /tmp/sandbox060731990/main.go:13 +0x280
main.main()  
    /tmp/sandbox060731990/main.go:22 +0xc0
```

#### recover

> recover 是一个内建函数，用于重新获得 panic 协程的控制。

* 只有在延迟函数的内部，调用 recover 才有用
* 在延迟函数内调用 recover，可以取到 panic 的错误信息，并且停止 panic 续发事件（Panicking Sequence），程序运行恢复正常
* 如果在延迟函数的外部调用 recover，就不能停止 panic 续发事件。

```go
package main

import (  
    "fmt"
)

// recoverName() 函数调用了 recover()，返回了调用 panic 的传参
func recoverName() {  
    if r := recover(); r!= nil {
        fmt.Println("recovered from ", r)
    }
}

func fullName(firstName *string, lastName *string) {  

    // 当 fullName 发生 panic 时，会调用延迟函数 recoverName()，
    // 它使用了 recover() 来停止 panic 续发事件。
    defer recoverName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

```go
recovered from  runtime error: last name cannot be nil  
returned normally from main  
deferred call in main
```

#### panic，recover 和 Go 协程

> 只有在相同的 Go 协程中调用 recover 才管用。recover 不能恢复一个不同协程的 panic

```go
package main

import (  
    "fmt"
    "time"
)

func recovery() {  
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}

func a() {  
    defer recovery()
    fmt.Println("Inside A")
    go b()                     // 不在同一个协程
    time.Sleep(1 * time.Second)
}

func b() {  
    fmt.Println("Inside B")
    panic("oh! B panicked")
}

func main() {  
    a()
    fmt.Println("normally returned from main")
}
```

```go
Inside A  
Inside B  
panic: oh! B panicked

goroutine 5 [running]:  
main.b()  
    /tmp/sandbox388039916/main.go:23 +0x80
created by main.a  
    /tmp/sandbox388039916/main.go:17 +0xc0
```

> 由 go b() 修改为 b()，就可以恢复 panic 了， 因为 panic 发生在与 recover 相同的协程里。

#### 恢复后获得堆栈跟踪

> 当我们恢复 panic 时，我们就释放了它的堆栈跟踪。实际上，在上述程序里，恢复 panic 之后，我们就失去了堆栈跟踪。

> 有办法可以打印出堆栈跟踪，就是使用 Debug 包中的 PrintStack 函数。

```go
import (  
    "fmt"
    "runtime/debug"
)

func r() {  
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
        debug.PrintStack()
    }
}

func a() {  
    defer r()
    n := []int{5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}

func main() {  
    a()
    fmt.Println("normally returned from main")
}
```

```go
// 首先已经恢复了 panic，打印出 Recovered runtime error: index out of range。此外，我们也打印出了堆栈跟踪。在恢复了 panic 之后，还打印出 normally returned from main。

Recovered runtime error: index out of range  
goroutine 1 [running]:  
runtime/debug.Stack(0x1042beb8, 0x2, 0x2, 0x1c)  
    /usr/local/go/src/runtime/debug/stack.go:24 +0xc0
runtime/debug.PrintStack()  
    /usr/local/go/src/runtime/debug/stack.go:16 +0x20
main.r()  
    /tmp/sandbox949178097/main.go:11 +0xe0
panic(0xf0a80, 0x17cd50)  
    /usr/local/go/src/runtime/panic.go:491 +0x2c0
main.a()  
    /tmp/sandbox949178097/main.go:18 +0x80
main.main()  
    /tmp/sandbox949178097/main.go:23 +0x20
normally returned from main
```

### 头等函数

> 可以把函数赋值给变量，也可以把函数作为其他函数的参数或者返回值

#### 匿名函数

* 1、 匿名函数赋值变量

```go
package main

import "fmt"

func main() {
    a := func() {
        fmt.Println("Hello wolrd")
    }

    a()
    fmt.Printf("%T", a)
}
```

```go
Hello wolrd
func()
```

* 2、匿名函数立即调用

```go
package main

import (  
    "fmt"
)

func main() {  
    func() {
        fmt.Println("hello world first class function")
    }()
}
```

```go
hello world first class function
```

* 3、向匿名函数传递参数

```go
package main

import (  
    "fmt"
)

func main() {  
    func(n string) {
        fmt.Println("Welcome", n)
    }("Gophers")
}
```

```go
Welcome Gophers
```

#### 自定义的函数类型

```go
package main

import "fmt"

// 创建一个新的函数类型 add, 接收两个整型类型，并返回一个整型
type add func(a int, b int) int


func main() {
    var a add = func(a int, b int) int {
        return a + b
    }
    s := a(5, 6)
    fmt.Println("Sum", s)  // Sum 11
}
```

#### 高阶函数

高阶函数（Hiher-order Function）定义为：满足下列条件之一的函数：

* 接收一个或多个函数作为参数
* 返回值是一个函数
* 把函数作为参数，传递给其它函数

```go
package main

import "fmt"

// 函数 simple 接收一个函数参数（该函数接收两个 int 参数，返回一个 a 整型）
func simple(a func(a, b int) int) {
    fmt.Println(a(60,7))
}

func main() {  
    // 创建了一个匿名函数 f，其签名符合 simple 函数的参数
    f := func(a, b int) int {
        return a + b
    }
    simple(f)  // 67
}
```

* 在其它函数中返回函数

```go
package main

import (  
    "fmt"
)

// simple 函数返回了一个函数，并接受两个 int 参数，返回一个 int。
func simple() func(a, b int) int {  
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {  
    /*
    调用了 simple 函数。我们把 simple 的返回值赋值给了 s
    调用了 s，并向它传递了两个 int 参数
     */ 
    s := simple()
    fmt.Println(s(60, 7))  // 67
}
```

#### 闭包

> 当一个函数所访问的变量定义在函数体的外部时,称这样的匿名函数为闭包

```go
package main

import "fmt"


func main() {
    a := 5
    // 访问了变量 a，而 a 存在于函数体的外部。因此这个匿名函数就是闭包。
    func() {
        fmt.Println("a=",a)   // a= 5
    }()
}
```

* 每一个闭包都会绑定一个它自己的外围变量

```go
package main

import (  
    "fmt"
)


// 函数 appendStr 返回了一个闭包
func appendStr() func(string) string {  
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {  
    a := appendStr()
    b := appendStr()
    fmt.Println(a("World"))     // Hello World  
    fmt.Println(b("Everyone"))  // Hello Everyone

    fmt.Println(a("Gopher"))    // Hello World Gopher  
    fmt.Println(b("!"))         // Hello Everyone !
}
```

#### 头等函数的实际用途

```go
package main

import (  
    "fmt"
)

type student struct {  
    firstName string
    lastName  string
    grade     string
    country   string
}

/*
filter 的第二个参数是一个函数
这个函数接收 student 参数，返回一个 bool 值
*/ 
func filter(s []student, f func(student) bool) []student {  
    var r []student
    for _, v := range s {
        if f(v) == true {
            r = append(r, v)
        }
    }
    return r
}

func main() {  
    s1 := student{
        firstName: "Naveen",
        lastName:  "Ramanathan",
        grade:     "A",
        country:   "India",
    }
    s2 := student{
        firstName: "Samuel",
        lastName:  "Johnson",
        grade:     "B",
        country:   "USA",
    }
    s := []student{s1, s2}

    // 这个函数作为参数传递给了 filter 函数
    f := filter(s, func(s student) bool {
        if s.grade == "B" {
            return true
        }
        return false
    })
    fmt.Println(f)
}
```

示例二： 对切片的每个元素执行相同的操作，并返回结果

```go
package main

import (  
    "fmt"
)


func iMap(s []int, f func(int) int) []int {  
    var r []int
    for _, v := range s {
        r = append(r, f(v))
    }
    return r
}


func main() {  
    a := []int{5, 6, 7, 8, 9}
    r := iMap(a, func(n int) int {
        return n * 5
    })
    fmt.Println(r)   // [25 30 35 40 45]
}
```

### 反射

> 反射就是程序能够在运行时检查变量和值，求出它们的类型

#### reflect 包

* reflect.Type: 表示 interface{} 具体类型
* reflect.Value: 表示它的具体值
* reflect.TypeOf() 返回 reflect.Type
* reflect.ValueOf() 返回 reflect.Value
* reflect.Kind: 表示该类型的特定类别
* NumField(): 返回结构体中字段的数量
* Field(): 返回字段 i 的 reflect.Value。

> reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值

#### reflect.Type 和 reflect.Value

```go
// 通用查询生成器,可以适用于任何结构体类型

package main

import (
    "fmt"
    "reflect"
)

type order struct {
    ordId      int
    customerId int
}

// createQuery 函数接收 interface{} 作为参数
// 程序打印了接口的具体类型和具体值。
func createQuery(q interface{}) {
    t := reflect.TypeOf(q)    // reflect.TypeOf 接收了参数 interface{}，返回了reflect.Type
    v := reflect.ValueOf(q)   // 返回了 reflect.Value
    fmt.Println("Type ", t)   // Type  main.order
    fmt.Println("Value ", v)  // Value  {456 56}
}

func main() {
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}
```

#### reflect.Kind

> Type 表示 interface{} 的实际类型（在这里是 main.Order)，而 Kind 表示该类型的特定类别

```go
package main

import (
    "fmt"
    "reflect"
)

type order struct {
    ordId      int
    customerId int
}

func createQuery(q interface{}) {
    t := reflect.TypeOf(q)     // 实际类型main.order
    k := t.Kind()              // 该类型的特定类别（在这里是 struct）。
    fmt.Println("Type ", t)    // Type  main.order
    fmt.Println("Kind ", k)    // Kind  struct


}
func main() {
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}
```

#### NumField() 和 Field() 方法

```go
package main

import (
    "fmt"
    "reflect"
)

type order struct {
    ordId      int
    customerId int
}

func createQuery(q interface{}) {
    /*
    1、判断类型是否是结构体
    2、NumField 方法只能在结构体上使用
    */
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        v := reflect.ValueOf(q)
        fmt.Println("Number of fields", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
        }
    }

}
func main() {
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)
}
```

```go
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
```

#### Int() 和 String() 方法

> Int 和 String 可以帮助我们分别取出 reflect.Value 作为 int64 和 string。

```go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 56
    x := reflect.ValueOf(a).Int()
    fmt.Printf("type:%T value:%v\n", x, x)  // type:int64 value:56

    b := "Naveen"
    y := reflect.ValueOf(b).String()
    fmt.Printf("type:%T value:%v\n", y, y)  // type:string value:Naveen

}
```

#### 完整实例(通用查询器)

```go
package main

import (
    "fmt"
    "reflect"
)

type order struct {
    ordId      int
    customerId int
}

type employee struct {
    name    string
    id      int
    address string
    salary  int
    country string
}

func createQuery(q interface{}) {
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        t := reflect.TypeOf(q).Name()
        query := fmt.Sprintf("insert into %s values(", t)
        v := reflect.ValueOf(q)
        for i := 0; i < v.NumField(); i++ {
            switch v.Field(i).Kind() {
            case reflect.Int:
                if i == 0 {
                    query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
                } else {
                    query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
                }
            case reflect.String:
                if i == 0 {
                    query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
                } else {
                    query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
                }
            default:
                fmt.Println("Unsupported type")
                return
            }
        }
        query = fmt.Sprintf("%s)", query)
        fmt.Println(query)
        return

    }
    fmt.Println("unsupported type")
}

func main() {
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

    e := employee{
        name:    "Naveen",
        id:      565,
        address: "Coimbatore",
        salary:  90000,
        country: "India",
    }
    createQuery(e)
    i := 90
    createQuery(i)

}
```

### Map(集合)

> Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据，key 类似于索引，指向数据的值。

#### 语法

```go
/* 声明变量， 默认 map 是 nil */
var map_variable map[key_data_type]value_data_type


/* 使用 make 函数*/
map_variable := make(map[key_data_type]value_data_type) 


// 如果不初始化 map，那么就会创建一个 nil map。nil map 不能用来存放键值对
```

#### map 示例

```go
package main 


import "fmt"

func main() {
    var contryMap map[string]string   // 创建集合
    contryMap = make(map[string]string)

    // map 插入key - value 对
    contryMap ["中国"] = "北京" 
    contryMap ["美国"] = "纽约" 
    contryMap ["泰国"] = "曼谷" 
    contryMap ["英国"] = "伦敦"

    // 使用键输出对应的首都地址
    for contry := range contryMap {
        fmt.Println(contry, " 首都是", contryMap [contry])
    } 

    // 查看元素在集合中是否存在
    capital, ok := contryMap ["迪拜"]

    if(ok) {
        fmt.Println("迪拜 首都是", capital)
    } else {
        fmt.Println("迪拜 首都不存在")
    }
}
```

```go
英国  首都是 伦敦
中国  首都是 北京
美国  首都是 纽约
泰国  首都是 曼谷
迪拜 首都不存在
```

#### delete 函数

> delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key

```go
package main

import "fmt"

func main() {
        /* 创建map */
        countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}


        /*删除元素*/ 
        delete(countryCapitalMap, "France")


        /*打印地图*/
        for country := range countryCapitalMap {
                fmt.Println(country, "首都是", countryCapitalMap [ country ])
        }
}
```

```go
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi
```
