Go 基础

参考链接

[Golang教程 - 菜鸟教程](Go 基础教程 - Golang教程 - 菜鸟教程l)

Go 系列教程 —— 34. 反射 - Go语言中文网 - Golang中文社区

Hello world

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个字母的最佳长度。

例如:

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

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

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个)

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 : 只可以打印出格式化的字符串,可以输出字符串类型的变量

//使用字符串
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关键字

    • 使用短变量声明运算符(:=)---> 只能在局部使用,不能全局

  • 声明变量

    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 关键字作为前缀来声明具有特定类型的常量。不能使用 := 语法声明。

  • 典型

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。

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
}

关系运算符

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

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), ||(逻辑或), !(逻辑非)

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):(按位清除)运算符,该运算符的实际操作为&(^)操作。

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
}

赋值运算符

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

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

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
}

杂项运算符

  • &:此运算符返回变量的地址。

  • *:此运算符提供指向变量的指针。

  • <-:该运算符的名称为接收。它用于从通道接收值。

package main 
import "fmt"

func main() { 
    a := 23

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

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

Go 类型转换

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

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 条件判断

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循环的迭代次数为零。

  • 语法:

for initialization; condition; post{
       // 语句....
}
  • initialization: 可选,循环开始之前执行(如变量声明,递增,赋值,函数)

  • condition: 布尔表达式,该表达式在循环的每次迭代开始时计算。如果条件语句的值为true,则执行循环。

  • post: for循环体之后执行。在post语句之后,条件语句再次计算条件语句的值是否为false,然后循环结束

for简单循环

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无线循环

package main 
import "fmt"

func main() { 

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

}

for循环用作while循环

package main 

import "fmt"

func main() { 

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

for循环数组

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循环字符串

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的键和值对

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通道

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 ---> 表示无条件跳转到标号处执行。

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

语法:

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

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

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语句不带可选语句和表达式

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多个值

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

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 避免死锁

  • 语法

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

设置默认值

package main
import "fmt"

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

Go 函数

简单函数

语法

func function_name(Parameter_list)(Return_type){
    // function body.....
}
  • func: Go关键字,用于创建函数

  • function_name: 函数名称

  • Parmeter_list: 包含函数参数名称和类型

  • Return_type: 可选,包含函数返回值类型,如果在函数中使用return_type,则必须在函数中使用return语句。

package main
import "fmt"

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



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

可变参函数

  • 当您要在函数中传递切片时,使用可变参数函数。

  • 当我们不知道参数的数量时,使用可变参数函数。

  • 在程序中使用可变参数函数时,它可以增加程序的可读性。

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...)) 
}

匿名函数

  • 定义格式

func(参数列表)(返回参数列表){
    函数体
}
  • 在定义时调用匿名函数

func(data int) {
    fmt.Println("hello", data)
}(100)  // (100),表示对匿名函数进行调用,传递参数为 100。
  • 将匿名函数赋值给变量

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

f(100)
  • 匿名函数用作回调函数

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

  • 简单实用

package main
import (
    "fmt"
)

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

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

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

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)
}
  • 从另一个函数返回匿名函数。(常用于回调函数)

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 函数调用

按值调用

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

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
}

引用调用

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

package main

import (
    "fmt"
)



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


func main() {
    var Zz int = 10

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

    fmt.Println(Zz) // 70
}

函数返回值

  • 首先先看下函数语法

func function_name(parameter_list)(return_type_list){
     // code...
}
  • function_name:它是函数的名称。

  • parameter-list:它包含函数参数的名称和类型。

  • return_type_list:这是可选的,它包含函数返回的值的类型。如果在函数中使用return_type,则必须在函数中使用return语句。

返回多个值

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
}

返回值命名

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

  • 语法

// 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...
}
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)。

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()函数的主要目的是初始化无法在全局上下文中初始化的全局变量。

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语句通常用于确保在完成文件处理后关闭文件,关闭通道或捕获程序中的紧急情况。

  • 语法

// 函数
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
}()
  • 后进先出延迟执行

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 数组

  • 语法

var array_name[length]Type

var array_name[length]Typle{item1, item2, item3, ...itemN}

var创建数组

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
}

简声明创建数组

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

import "fmt"



func main() {

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

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

多维数组

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

  • 语法

Array_name[Length1][Length2]..[LengthN]Type
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
------------

省略号

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

//数组中省略号的使用方法
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
}

数组比较

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语句通常用于确保在完成文件处理后关闭文件,关闭通道或捕获程序中的紧急情况。

  • 语法

// 函数
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
}()
  • 后进先出延迟执行

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 数组

  • 语法

var array_name[length]Type

var array_name[length]Typle{item1, item2, item3, ...itemN}

使用var关键字创建数组

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
}

使用简声明

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

import "fmt"



func main() {

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

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

多维数组

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

  • 语法

Array_name[Length1][Length2]..[LengthN]Type
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
------------

省略号

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

//数组中省略号的使用方法
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
}

数组比较

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

}

数组复制

  • 语法

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


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

数组作为函数参数

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

  • 语法

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

//对于无大小的数组
func function_name(variable_name []type){
// Code
}
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}

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 容量(可选,默认和长度一样))

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

}

遍历切片方法

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)
    }
}

多维切片使用

package main

import "fmt"



func main() {

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

    fmt.Println(_slice3)


}

切片比较判断

  • 判断两个[]byte是否相等

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

  • 牺牲了性能

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 快

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和字符串切片进行排序的不同类型的排序方法。 这些函数始终按升序对可用元素进行切片排序

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]

}

切片分割

  • 语法

    func Split(o_slice, sep []byte) [][]byte
    o_slice是字节片
    sep是分隔符。
    
    如果sep为空,则它将在每个UTF-8序列之后拆分

切片传递给函数

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]
}

字符串操作

字符串比较

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
}

是否存在某个字符或子串

  • 语法

// 子串 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
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 是计算子串在字符串中出现的无重叠的次数

  • 语法

func Count(s, sep string) int
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。

  • 语法

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

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

package main

import (
    "fmt"
    "strings"
)

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

// 结果
Field:  ["a" "b" "c"]
  • FieldsFunc

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

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) 等价。

  • 语法

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) }
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"]

}

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

package main

import (
    "fmt"
    "strings"
)

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

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

// 在 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 实现,函数签名如下:

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

  • 语法

func Repeat(s string, count int) string
fmt.Println("ba" + strings.Repeat("na", 2))

// 输出结果
banana

字符替换

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

字符串子串替换

  • 语法

// 用 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
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

大小写转换

  • 语法

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 的每个字母大写,并且会将一些特殊字母转换为其对应的特殊大写字母。
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, "āáǎà ōóǒò êēéěè")) // 全部大写包含特殊字符
}

修剪

// 将 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
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 类型

进行多个替换

r := strings.NewReplacer("<", "<", ">", ">") // < 替换成 <  > 替换成 >
fmt.Println(r.Replace("This is <b>HTML</b>!"))     // This is <b>HTML</b>!
  • 替换结果写入到文件

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

Go 语言结构体(struct)

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

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

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。并且,如果比较的变量属于不同的结构,则编译器将给出错误

// 使用 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 嵌套结构体

  • 语法

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

}
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 匿名结构和字段

  • 匿名结构

package main

import (
    "fmt"
)


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

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


    _Student

}
  • 匿名字段

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
}
  • 不允许

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

结构体内嵌类型

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结构中的一个字段
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结构中的一个字段
package main 

import "fmt"



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

}

指针解引用

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

import "fmt"



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

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

}

传递指针给函数

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

import "fmt"


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

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

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

}

函数返回指针地址

package main 

import "fmt"

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


func main() {

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

结构体指针

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。

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)
}

指针容量和长度

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 方法接收器

方法简单示例

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 个字段

等同于

// 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
}

在结构上定义接收器方法

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
}

指针接收器与值接收器

  • 指针接收器: 考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。

  • 值接收器: 除了指针接收器外,在其他的所有情况,都可以被使用值接收器

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
}

匿名字段的方法

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
}

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

  • 当一个函数有一个值参数,它只能接受一个值参数

  • 当一个方法有一个值接收器,它可以接受值接收器和指针接收器。

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()//通过指针调用值接收器
}

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

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

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
}

在非结构体上的方法

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 接口

  • 静态接口

  • 动态接口

  • 语法:

type interface_name interface{

    //方法签名
}

接口的声明与实现

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]
}

接口的实际用途

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

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。

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{}。由于空接口没有方法,因此所有类型都实现了空接口。

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)

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 代替

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)
}

类型与接口比较

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

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
}

指针接受者与值接受者

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)并不能取到地址,
*/
}

实现多个接口

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。

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>
    }
}

结合值和接口使用

指针类型实现接口:

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
}

值类型实现接口:

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
}

通过函数封装接口实现:

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 协程也不会继续运行。

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 微秒后终止程序。

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 协程间的通信。

  • 信道的声明

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

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
    }

}
  • 信道进行发送和接收

默认是阻塞

data := <- a   // 读取信道 a
a <- data      // 写入信道 a
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 主协程把平方和与立方和相加。

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,形成死锁。

package main

func main() {  
    ch := make(chan int)
    ch <- 5
}
// 错误信息
fatal error: all goroutines are asleep - deadlock!

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

单向信道

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

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

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 的值。

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 循环用于在一个信道关闭之前,从信道接收数据。

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)
    }
}
Received  0
Received  1
Received  2
Received  3
Received  4
Received  5
Received  6
Received  7
Received  8
Received  9

缓冲信道

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

  • 语法

// 信道有缓冲 capacity 应该大于 0。无缓冲信道的容量默认为 
ch := make(chan type, capacity)
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)
    }
}
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

死锁

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

fatal error: all goroutines are asleep - deadlock!

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

长度 vs 容量

  • 缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。

  • 缓冲信道的长度是指信道中当前排队的元素个数。

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 来实现。

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 协程的执行顺序不一定
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、从输出型缓冲信道读取并打印结果

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")
}
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 语句一直阻塞。

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,然后程序终止。该程序会输出:
*/ 

死锁与默认情况

package main

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    }
}
// 创建了一个信道 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 准备就绪时,会执行默认情况。我们用默认情况重写后

package main

import "fmt"

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

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

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 准备就绪时,将会随机地选取其中之一去执行。

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,因此它会一直阻塞,导致死锁。

ackage main

func main() {  
    select {}
}

Mutex(加锁)

  • 临界区: 当程序并发地运行时,多个 Go 协程不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区

  • Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件。

  • 方法

    • mutex.Lock()

    • mutex.Unlock()

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

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

使用 Mutex

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

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

}

使用信道处理竞态条件

  • 信道来处理竞态条件

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

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

employee.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

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)。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子:一辆汽车由车轮、引擎和其他各种部件组合在一起。

通过嵌套结构体进行组合

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

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()
}
Title:  标题
Content:  内容
Author:  张 三
Bio:  Golang

结构体切片的嵌套

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

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()
}
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。该组织的净收益等于这两个项目的收入总和

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)
}
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organisation = $19000

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

// 上面代码添加
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)
}
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() 方法。

错误处理

示例

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())
}

错误类型的表示

type error interface {  
    Error() string
}

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

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

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 类型,并调用这些方法来确定该错误是临时性错误,还是由超时导致的。

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 相比较。如果该条件满足,那么该错误就是由模式错误导致的

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、不可忽略错误

package main

import (  
    "fmt"
    "path/filepath"
)

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

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

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 函数会根据格式说明符,规定错误的格式,并返回一个符合该错误的字符串。

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)
}

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

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)
}

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

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

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




// 有了错误类型,我们来实现 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)
}
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 示例

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")
}
// 当出现了 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 时,首先执行了延迟函数,接着控制返回到函数调用方,调用方的延迟函数继续运行,直到到达顶层调用函数。

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")
}
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 续发事件。

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")
}
recovered from  runtime error: last name cannot be nil  
returned normally from main  
deferred call in main

panic,recover 和 Go 协程

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

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")
}
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 函数。

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")
}
// 首先已经恢复了 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、 匿名函数赋值变量

package main

import "fmt"

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

    a()
    fmt.Printf("%T", a)
}
Hello wolrd
func()
  • 2、匿名函数立即调用

package main

import (  
    "fmt"
)

func main() {  
    func() {
        fmt.Println("hello world first class function")
    }()
}
hello world first class function
  • 3、向匿名函数传递参数

package main

import (  
    "fmt"
)

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

自定义的函数类型

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)定义为:满足下列条件之一的函数:

  • 接收一个或多个函数作为参数

  • 返回值是一个函数

  • 把函数作为参数,传递给其它函数

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
}
  • 在其它函数中返回函数

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
}

闭包

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

package main

import "fmt"


func main() {
    a := 5
    // 访问了变量 a,而 a 存在于函数体的外部。因此这个匿名函数就是闭包。
    func() {
        fmt.Println("a=",a)   // a= 5
    }()
}
  • 每一个闭包都会绑定一个它自己的外围变量

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 !
}

头等函数的实际用途

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)
}

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

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

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

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 表示该类型的特定类别

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() 方法

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)
}
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。

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

}

完整实例(通用查询器)

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 类似于索引,指向数据的值。

语法

/* 声明变量, 默认 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 示例

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("迪拜 首都不存在")
    }
}
英国  首都是 伦敦
中国  首都是 北京
美国  首都是 纽约
泰国  首都是 曼谷
迪拜 首都不存在

delete 函数

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

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 ])
        }
}
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi

Last updated