1. 开始学习Go
从一本书开始,这本书叫做《Go语言从入门到进阶实战(视频教学版)》,当然这篇Blog并不是所有的内容都来自这本书,毕竟书中也有不足之处。
![]()
2. Hello World
学啥语言的第一步都是Hello World, 第一步是搭建开发环境,直接下载Goland,创建一个新的工程,点击create
![]()
建好项目以后,项目应该是下面这个样子的,当然我们不用关注这个go.mod是干什么的,我们只是利用IDE编写代码,使用命令行运行。
1 2 3 4 5 6
   | s@HELLOWANG-MB1 go-study % tree . `-- go.mod
  0 directories, 1 file
 
   | 
 
然后新建一个main.go的文件,其内容如下
1 2 3 4 5
   | package main import "fmt" func main() {     fmt.Println("hello world") }
   | 
 
最后运行该文件
1 2
   | s@HELLOWANG-MB1 go-study % go run main.go hello world
   | 
 
我们也可以编译为二进制文件,然后运行
1 2 3 4 5
   | s@HELLOWANG-MB1 go-study % go build main.go s@HELLOWANG-MB1 go-study % ls go.mod  main    main.go s@HELLOWANG-MB1 go-study % ./main hello world
   | 
 
3. Go的变量声明
变量的声明方式为var <变量名> <变量类型>
例如下面的程序
1 2 3 4 5 6 7 8 9
   | package main
  import "fmt"
  func main() {     var a int      fmt.Println(a) }
 
   | 
 
经过了命令行运行以后,可以转而使用IDE集成开发,点击按钮▶️
![]()
当然,还有更多的声明方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | package main
  import "fmt"
  func main() {     var a int      fmt.Println(a)
      var b int = 1      fmt.Println(b)
      var c = 2      fmt.Println(c)
      d := 3      fmt.Println(d) }
 
   | 
 
最后还有一个小技巧,即函数返回可以是多个变量,用逗号分隔, _表示匿名变量,即忽略该位置的值。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | package main
  import "fmt"
  func main() {     a := 1     b := 2     a, b = b, a     c, _ := a, b     _, d := a, b     fmt.Println(a, b, c, d) }
 
   | 
 
4. Go 的基础数据类型
4.1. 整形
有符号整型: int8 int16 int32 int64
无符号整型: uint8 uint16 uint32 uint64
平台自适应整形: int uint(自动根据平台决定整形的长度)
4.2. 浮点数
float32 float64
1 2 3 4 5 6 7 8 9 10
   | package main
  import "fmt"
  func main() {     var a float32 = 1.5     var b float64 = 1.5     fmt.Println(a, b) }
 
   | 
 
4.3. 布尔型
1 2 3 4 5 6 7 8 9
   | package main
  import "fmt"
  func main() {     var a bool = true     fmt.Println(a) }
 
   | 
 
4.4. 字符串
1 2 3 4 5 6 7 8 9
   | package main
  import "fmt"
  func main() {     var a string = "123"     fmt.Println(a) }
 
   | 
 
多行字符串
1 2 3 4 5 6 7 8 9 10 11 12
   | package main
  import "fmt"
  func main() {     var a string = `  123你好     abcsdaf aslfnskflasjlfjoiwn     `     fmt.Println(a) }
 
   | 
 
5. Go的控制流
5.1. 循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | package main
  import "fmt"
  func main() {
           a := 1     for a <= 3 {         fmt.Println(a)         a++     }
           for {         fmt.Println("break")         break     }
           for i := 0; i < 3; i++ {         fmt.Println("hello")     }
  }
 
   | 
 
5.2. 条件判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | package main
  import "fmt"
  func main() {
      if 1+1 == 2 {         fmt.Println("1+1=2")     } else {         fmt.Println("1+1!=2")     }
  }
 
   | 
 
5.3. switch
需要注意的是Go的switch自带break,下面的程序只会输出3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | package main
  import "fmt"
  func main() {
      switch 3 {     case 3:         fmt.Println(3)     case 2:         fmt.Println(2)     case 1:         fmt.Println(1)     }
  }
 
   | 
 
6. Go的容器/集合
6.1. Map
注意map需要指定key和value 的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | package main
  import "fmt"
  func main() {     mp := make(map[string]int)     mp["k1"] = 1     mp["k2"] = 2     fmt.Println(mp)
      delete(mp,"k1")     fmt.Println(mp)      }
 
   | 
 
6.2. 数组
数组长度固定, 创建数组也有很多种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | package main
  import "fmt"
  func main() {     var arr1 []int = make([]int, 3)     arr1[0] = 1
      arr2 := [3]int{1, 2}     arr3 := [...]int{1, 2}
      fmt.Println(arr1, arr2, arr3) }
 
 
   | 
 
注意对于由字面量组成的数组,如果长度小于等于4,那么它将直接被分配到栈上,否则分配到静态区
6.3. 切片
切片的创建可以是数组的一部分,也可以直接创建,切片可扩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | package main
  import "fmt"
  func main() {     var arr1 []int = make([]int, 3) 
      slice1 := arr1[1:2]     slice2 := []int{1, 2, 3}     slice3 := make([]int, 3)
      fmt.Println(slice1, slice2, slice3) }
 
 
 
   | 
 
那么切片和数组有什么区别呢?其实切片只是数组的一个引用,任何一个切片,其背后一定有一个数组,当切片进行扩容的时候,会根据数组的剩余空间大小来决定附身到新的数组上,或者直接在原数组上扩容切片。
具体表现如下, 输出就在注释里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | package main
  import "fmt"
  func main() {     arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      slice1 := arr1[2:5]     slice2 := arr1[3:6]
      fmt.Println(slice1, slice2)     arr1[4]=0     fmt.Println(slice1, slice2) }
 
 
 
   | 
 
6.3. 列表
列表可以自动伸缩
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | package main
  import (     "container/list"     "fmt" )
  func main() {     ls := list.New()     ls.PushBack(1)     ls.PushBack(2)     fmt.Println(ls) }
 
   | 
 
7. Go的函数
返回值写在参数后面
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | package main
  import "fmt"
  func plus(a int, b int) int {     return a + b }
  func main() {     a := 1     b := 1     fmt.Println(plus(a, b)) }
 
   | 
 
还可以选择返回多个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | package main
  import "fmt"
  func swap(a int, b int) (int, int) {     return b, a }
  func main() {     a := 1     b := 2     fmt.Println(swap(a, b)) }
 
   | 
 
变长的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | package main
  import "fmt"
  func sum(a ...int) int {     s := 0     for i := 0; i < len(a); i++ {         s += a[i]     }     return s }
  func main() {     fmt.Println(sum(1, 2, 3))
      nums := []int{1, 2, 3, 4}     fmt.Println(sum(nums...)) }
 
   | 
 
返回一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | package main
  import "fmt"
  func increase() func() int {     i := 0     return func() int {         i++         return i     } }
  func main() {     fun := increase()     fmt.Println(fun())     fmt.Println(fun())     fmt.Println(fun())     fmt.Println(fun())
  }
   | 
 
8. Go的指针
和C一样
9. Go 的结构体
9.1. 结构体定义
1 2 3 4 5
   | type <类型名> struct {     <字段1名> <字段1类型>     <字段2名> <字段2类型>     <字段3名> <字段3类型> }
  | 
 
例子:
1 2 3 4
   | type Point struct {     X int32     Y int32 }
  | 
 
9.2. 结构体实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | package main
  import "fmt"
  type Point struct {     X int32     Y int32 }
  func main() {     var p Point     p.X = 1     p.Y = 2     fmt.Println(p.X, p.Y) }
 
   | 
 
使用new实例化,注意此时得到的p2是指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | package main
  import "fmt"
  type Point struct {     X int32     Y int32 }
  func main() {     p2 := new(Point)     fmt.Println(p2) }
 
   | 
 
9.3. 结构体函数
结构体函数定义比普通函数定义在func和函数名之间多了一个结构体对象,这个对象一般使用指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | package main
  import "fmt"
  type Point struct {     X int32     Y int32 }
  func (p *Point) getAndSet(X int32, Y int32) (int32, int32) {     p.X, X = X, p.X      p.Y, Y = Y, p.Y      return X, Y }
  func main() {     p := new(Point)     p.X, p.Y = 1, 2     fmt.Println(p.getAndSet(3, 4))     fmt.Println(p.X, p.Y) }
 
 
   | 
 
9.4. 结构体的Tag
结构体中的字段可以设置Tag,即给字段打上标签,就像Java中的注解一样。然后可以使用一种比较高级的技术(反射来获取这个标签)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | package main
  import "reflect"
  type Point struct {     X int `name:"XXX" X:"你好"`     Y int `name:"YYY"` }
  func main() {     p := &Point{         X: 1,         Y: 2,     }
      field, _ := reflect.TypeOf(*p).FieldByName("X")
      println(field.Tag) }
 
 
   | 
 
10. Go的接口
接口定义
1 2 3 4
   | type <接口类型名> interface{     <方法1名> (<参数列表1>) 返回值列表1     <方法2名> (<参数列表2>) 返回值列表2 }
  | 
 
让结构体实现接口, 只需要让结构体的函数与接口保持一致即可,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | package main
  import "fmt"
  type Point struct {     X int32     Y int32 }
  type GetAndSet interface {     invoke(X int32, Y int32) (int32, int32) }
  func (p *Point) invoke(X int32, Y int32) (int32, int32) {     p.X, X = X, p.X      p.Y, Y = Y, p.Y      return X, Y }
  func main() {     p := new(Point)     p.X, p.Y = 1, 2
      var it GetAndSet = p     fmt.Println(it.invoke(3, 4))     fmt.Println(p.X, p.Y) }
 
   | 
 
11. Go的包
回到最开始的Helloworld,注意到第一行中的package main, 在Go中,有这样一个约定
包名为main的包为应用程序的入口包,编译源码没有main包时,将无法编译输出可执行的文件。
1 2 3 4 5
   | package main import "fmt" func main() {     fmt.Println("hello world") }
   | 
 
11.1. Go的包导出
在Go中,首字母为小写的变量只能在包内使用,首字母为大写的变量会自动导出,可以在其他包使用。
这是第一个文件mylib/mylib.go
1 2 3 4 5 6 7 8 9 10
   | package mylib
  func Add(a, b int32) int32 {     return a + b }
  func add(a, b int32) int32 {     return a + b }
 
   | 
 
11.2. Go的包导入
然后是main.go, 注意到可以直接使用Add函数,但是不能使用add函数
1 2 3 4 5 6 7 8 9 10 11 12
   | package main
  import (     "fmt"     "go-study/mylib" )
  func main() {     var a, b int32 = 1, 2     fmt.Println(mylib.Add(a, b)) }
 
   | 
 
在导入的时候可以直接重命名,只需要在包名前加上一个名字即可
1 2 3 4 5 6 7 8 9 10 11
   | package main
  import (     "fmt"     lb "go-study/mylib" )
  func main() {     var a, b int32 = 1, 2     fmt.Println(lb.Add(a, b)) }
   | 
 
11.3. Go的包的init函数
一个包的init函数在包被引入时自动调用, 对于main包,init函数在main函数前运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | package mylib
  import "fmt"
  func Add(a, b int32) int32 {     return a + b }
  func add(a, b int32) int32 {     return a + b }
  func init(){     fmt.Println("hi") }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | package main
  import (     "fmt"     lb "go-study/mylib" )
  func init() {     fmt.Println("hi main") }
  func main() {     var a, b int32 = 1, 2     fmt.Println(lb.Add(a, b)) }
 
   | 
 
12. Go的反射
12.1. Go反射类型
通过reflect包来进行反射,可以获得类型
1 2 3 4 5 6 7 8 9 10 11 12 13
   | package main
  import (     "fmt"     "reflect" )
  func main() {     a := 1     ta := reflect.TypeOf(a)     fmt.Println(ta.Bits(), ta.Name(), ta.Kind()) }
 
   | 
 
对于name和kind的区别,看看下面这份代码就行了, name为Point,kind为struct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | package main
  import (     "fmt"     "reflect" )
  type Point struct {     X, Y int32 }
  func main() {     var a = Point{}     ta := reflect.TypeOf(a)     fmt.Println(ta.Name(), ta.Kind()) }
 
   | 
 
12.2. Go反射值
通过字段的名字获取属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | package main
  import (     "fmt"     "reflect" )
  type Point struct {     X, Y int32 }
  func main() {     var a = Point{}     ta := reflect.ValueOf(a)     fmt.Println(ta.FieldByName("X")) }
 
 
   | 
 
13. Go的并发
13.1. goroutine
在关键词go后跟着一个函数调用,那么该函数调用就变成了goroutine,这是一个异步调用,立即返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | package main
  import (     "fmt"     "time" )
  func running() {     times := 1     for {         fmt.Println("tick", times)         times++         time.Sleep(time.Second)     } }
  func main() {     go running()     for {         time.Sleep(time.Second)     } }
 
   | 
 
13.2. Go的通道
下面的程序会依次输出0和hello, 通道先进先出 <-符号可以用来传输数据, 注意通道在发送和接受的时候都会阻塞,注意到最后一行有一个time.Sleep(time.Second),这是为了等待goroutine完成,否则main退出以后goroutine会直接强制退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | package main
  import (     "fmt"     "time" )
  func main() {     ch := make(chan interface{})
      go func() {         ch <- 0         fmt.Println("send: 0")         ch <- "hello"         fmt.Println("send: hello")     }()
      time.Sleep(time.Second)
      data := <-ch     fmt.Println("recv: ", data)
      time.Sleep(time.Second)
      data = <-ch     fmt.Println("recv: ", data)
      time.Sleep(time.Second) }
   | 
 
另一种接受方法是使用for循环,代码如下,注意这个循环需要手动退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | package main
  import (     "fmt"     "time" )
  func main() {     ch := make(chan interface{})
      go func() {         ch <- 0         fmt.Println("send: 0")         ch <- "hello"         fmt.Println("send: hello")         ch <- "break"         fmt.Println("send: break")     }()
      for data := range ch {         fmt.Println("recv:", data)         if data == "break" {             break         }     }
      time.Sleep(time.Second) }
 
 
   | 
 
最后通道还支持指定输入端和输出端,输入端只能做输入,输出端只能做输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | package main
  import (     "fmt"     "time" )
  func main() {     ch := make(chan interface{})
      var send chan<- interface{} = ch     var recv <-chan interface{} = ch
      go func() {         send <- 0     }()
      data := <-recv     fmt.Println("recv:", data)
      time.Sleep(time.Second) }
 
   | 
 
创建带有缓冲区的通道, 只需要在make的第二个参数中填入数字即可
1
   | ch := make(chan interface{}, 10)
  | 
 
多路复用,使用select关键字,case区域写要选择的通道即可接收多个通道,下面的代码有时输出1,有时输出2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | package main
  import (     "fmt"     "time" )
  func main() {     ch1 := make(chan interface{})     ch2 := make(chan interface{})
      go func() {         ch1 <- 1     }()
      go func() {         ch2 <- 2     }()
      time.Sleep(time.Second)
      select {     case data := <-ch1:         fmt.Println("recv from ch1: ", data)     case data := <-ch2:         fmt.Println("recv from ch2: ", data)     }
      time.Sleep(time.Second) }
 
   |