1. 开始学习Go
从一本书开始,这本书叫做《Go语言从入门到进阶实战(视频教学版)》,当然这篇Blog并不是所有的内容都来自这本书,毕竟书中也有不足之处。

2. Hello World
学啥语言的第一步都是Hello World, 第一步是搭建开发环境,直接下载Goland,创建一个新的工程,点击create

建好项目以后,项目应该是下面这个样子的,当然我们不用关注这个go.mod是干什么的,我们只是利用IDE编写代码,使用命令行运行。
| 12
 3
 4
 5
 6
 
 | s@HELLOWANG-MB1 go-study % tree.
 `-- go.mod
 
 0 directories, 1 file
 
 
 | 
然后新建一个main.go的文件,其内容如下
| 12
 3
 4
 5
 
 | package mainimport "fmt"
 func main() {
 fmt.Println("hello world")
 }
 
 | 
最后运行该文件
| 12
 
 | s@HELLOWANG-MB1 go-study % go run main.gohello world
 
 | 
我们也可以编译为二进制文件,然后运行
| 12
 3
 4
 5
 
 | s@HELLOWANG-MB1 go-study % go build main.gos@HELLOWANG-MB1 go-study % ls
 go.mod  main    main.go
 s@HELLOWANG-MB1 go-study % ./main
 hello world
 
 | 
3. Go的变量声明
变量的声明方式为var <变量名> <变量类型>
例如下面的程序
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | package main
 import "fmt"
 
 func main() {
 var a int
 fmt.Println(a)
 }
 
 
 | 
经过了命令行运行以后,可以转而使用IDE集成开发,点击按钮▶️

当然,还有更多的声明方式
| 12
 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)
 }
 
 
 | 
最后还有一个小技巧,即函数返回可以是多个变量,用逗号分隔, _表示匿名变量,即忽略该位置的值。
| 12
 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
| 12
 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. 布尔型
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | package main
 import "fmt"
 
 func main() {
 var a bool = true
 fmt.Println(a)
 }
 
 
 | 
4.4. 字符串
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | package main
 import "fmt"
 
 func main() {
 var a string = "123"
 fmt.Println(a)
 }
 
 
 | 
多行字符串
| 12
 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. 循环
| 12
 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. 条件判断
| 12
 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
| 12
 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 的类型
| 12
 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. 数组
数组长度固定, 创建数组也有很多种写法
| 12
 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. 切片
切片的创建可以是数组的一部分,也可以直接创建,切片可扩容
| 12
 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)
 }
 
 
 
 
 | 
那么切片和数组有什么区别呢?其实切片只是数组的一个引用,任何一个切片,其背后一定有一个数组,当切片进行扩容的时候,会根据数组的剩余空间大小来决定附身到新的数组上,或者直接在原数组上扩容切片。
具体表现如下, 输出就在注释里面。
| 12
 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. 列表
列表可以自动伸缩
| 12
 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的函数
返回值写在参数后面
| 12
 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))
 }
 
 
 | 
还可以选择返回多个值
| 12
 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))
 }
 
 
 | 
变长的参数
| 12
 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...))
 }
 
 
 | 
返回一个函数
| 12
 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. 结构体定义
| 12
 3
 4
 5
 
 | type <类型名> struct {<字段1名> <字段1类型>
 <字段2名> <字段2类型>
 <字段3名> <字段3类型>
 }
 
 | 
例子:
| 12
 3
 4
 
 | type Point struct {X int32
 Y int32
 }
 
 | 
9.2. 结构体实例化
| 12
 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是指针
| 12
 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和函数名之间多了一个结构体对象,这个对象一般使用指针
| 12
 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中的注解一样。然后可以使用一种比较高级的技术(反射来获取这个标签)
| 12
 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的接口
接口定义
| 12
 3
 4
 
 | type <接口类型名> interface{<方法1名> (<参数列表1>) 返回值列表1
 <方法2名> (<参数列表2>) 返回值列表2
 }
 
 | 
让结构体实现接口, 只需要让结构体的函数与接口保持一致即可,
| 12
 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包时,将无法编译输出可执行的文件。
| 12
 3
 4
 5
 
 | package mainimport "fmt"
 func main() {
 fmt.Println("hello world")
 }
 
 | 
11.1. Go的包导出
在Go中,首字母为小写的变量只能在包内使用,首字母为大写的变量会自动导出,可以在其他包使用。
这是第一个文件mylib/mylib.go
| 12
 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函数
| 12
 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))
 }
 
 
 | 
在导入的时候可以直接重命名,只需要在包名前加上一个名字即可
| 12
 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函数前运行
| 12
 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")
 }
 
 | 
| 12
 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包来进行反射,可以获得类型
| 12
 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
| 12
 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反射值
通过字段的名字获取属性
| 12
 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,这是一个异步调用,立即返回
| 12
 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会直接强制退出
| 12
 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循环,代码如下,注意这个循环需要手动退出
| 12
 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)
 }
 
 
 
 | 
最后通道还支持指定输入端和输出端,输入端只能做输入,输出端只能做输出
| 12
 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
| 12
 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)
 }
 
 
 |