Go

Go

https://www.bilibili.com/video/BV1zR4y1t7Wj?t=9.53

https://www.bilibili.com/video/BV1ee411173m?t=577.1&p=97

Go语言数据类型转换 (biancheng.net)

简介

  • Go无类和继承的概念 通过接口实现多态性
  • go是编译型语言 自带编译器
  • go语言自带垃圾回收
  • go常见指令:
    • go run在编译后直接运行程序 不会生成可执行文件
    • go build编译

包和依赖管理

  • 包名可以与目录名不同
  • 一个go程序必须有且仅有一个main包
  • 导入的包不能含有代码中没有用到的包
  • 为了让本包中的函数能够被其他包调用,函数名的首字母需要大写。
  • 值得注意的是,包名和文件夹名不一定相同。在import的地方写的是你的文件名,在调用的时候使用的是包名。
  • 如果要编译成一个可执行程序文件,就需要将这个包声明为main;如果是写一个库,包名随意

标准库:

一般情况下,标准库会存放在 GOOS_$GOARCH/ 目录下。比如在 Windows 下,标准库的位置在Go语言根目录下的子目录 pkg_amd64 中;在 Linux 下,标准库在Go语言根目录下的子目录 pkg_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。

GOPATH

GOPATH是最原始的依赖管理方式,具体来说,在go1.1.1后才引入gomodule逐步淘汰gopath

在使用gopath的时候,需要进行如下设置

1
2
set GO111MODULE=off
set GOPATH=工程根目录

当在项目中引入了一个无用的包,go会按照如下的顺序寻找:

  • ./vendor
  • $goroot/src
  • $gopath/src

为什么要引入vendor?因为在一个工程下可能有不同的项目,而每个项目可能会用到不同的依赖版本,当发生冲突时,就需要在本项目新建vendor文件夹,go也会优先在vendor中寻找依赖。

GOMODULE

类似Java里面的Maven,把所有的第三方库下载在一个统一的文件夹,然后项目去调用即可。

在Go的环境变量中,有一个变量GO111MODULE负责Go Modules的开关

  • auto:若当前项目不在GOPATH下且当前或上一层文件夹有go.mod则使用Go Modules
  • on:开启Go Modules
  • off:关闭Go Modules

采用Go Modules后,下载的第三方库位于$GOPATH/pkg/mod

常见命令

1
2
3
4
go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名
go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH/pkg/mod目录)
go mod graph: 打印项目的模块依赖结构
go mod tidy :添加缺少的包,且删除无用的

为什么要引入go.sum?因为GO没有中心仓库,而是将依赖托管在各大平台,因此若其他人对依赖进行了更改,但是没有更改版本号,则可能会出现异常。因此,需要将包下载时对应的哈希值进行存储。

官方gosumdb:

1
go env -w GOSUMDB=sum.golang.org

基本语法

变量

变量基本类型:

  • bool

  • string

  • int、int8 int16、int32、int64

  • uint、uint8、uint16、uint32、uint64、uintptr

  • byte // uint8 的别名

  • rune // int32 的别名 代表一个 Unicode 码

  • float32、float64

  • complex64、complex128

初始化:变量一旦声明就会全部自动初始化

变量声明:

  • 标准格式

    1
    var 变量名 变量类型
  • 批量格式

    1
    2
    3
    4
    var (
    a int
    b string
    )
  • 简短格式

    不能提供数据类型,且只能在函数内部

    注意,这种方式要求变量必须未被声明过

    1
    名字 := 表达式
  • 匿名变量:_,不能在后序代码中使用,不会占用内存空间

作用域

  • 局部变量

    函数内的局部变量可以和全局变量名称相同,但是会优先考虑局部变量

  • 全局变量

    全局变量必须以var关键词开头 如果想在外部包中使用,则首字母必须大写

  • 形式参数

    就是定义函数时函数名后面括号中的变量,形式参数会作为函数的局部变量来使用

常量:const

输入输出

二进制%b,八进制%o,十六进制%x

字符串:

  • 反引号代表多行
  • 字符串拼接
    • 使用+
    • 使用fmt.Sprintf
    • 使用strings.Join
    • 使用bytes.Buffer

流程控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 判断语句
if a>b {
qqq
} else {
yyy
}
// 分支语句
switch var1 {
case v1: xxx
case vv2: yyy
default: zzz
}
// 循环语句
for i:=0,i<10,i++ {
xxx
}
var arr = [3]int{1, 2, 3}
for k, v := range arr {
fmt.Println(k, v)
}

函数

普通函数

1
2
3
func 函数名(参数) [返回值类型]{

}

例-单个返回值:

1
2
3
4
5
6
7
8
9
10
func main() {
fmt.Println(test(1, 2))
}
func test(a int, b int) int {
if a > b {
return a
} else {
return b
}
}

例-多个返回值:

1
2
3
4
5
6
7
8
9
10
func main() {
fmt.Println(test(1, 2))
}
func test(a int, b int) (int, int) {
if a > b {
return a, b
} else {
return b, a
}
}

方法(定义在struct上的函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Person struct {
name string
age int
}

func main() {
var p Person
p.name = "qy"
p.test()
}
func (p Person) test() {
fmt.Println(p.name)
}

go中的函数特性

  • 没有函数重载
  • 不能嵌套函数(除了匿名函数)

init函数:

  • 执行顺序:变量初始化-->init函数-->main函数
  • 没有输入值和返回值
  • 每个包或源文件可以有多个init函数
  • 不同包的init函数按照导入顺序执行

指针:

  • 不能进行偏移和运算
  • 声明:var var_name *int

数组/切片

数组和切片是类似的。数组的长度固定,切片的长度不固定,具有动态长度,可以通过append动态增加切片的长度。

另外,他们的初始化方式也有所不同。

  • 数组

    1
    2
    var arr [3]int         // 声明一个长度为3的整型数组
    arr = [3]int{1, 2, 3} // 初始化数组
  • 切片

    1
    2
    3
    var slice []int        // 声明一个切片
    slice = []int{1, 2, 3} // 使用切片字面量初始化切片
    slice = append(slice, 4, 5, 6) // 使用append函数向切片追加元素

面向对象

Golang是支持面向对象的特性,但实际上不是传统的面向对象,比如没有类的概念。

结构体

go没有面向对象的概念,但是可以用结构体来实现

普通结构体

1
2
3
4
5
6
7
8
9
10
11
type Person struct {
age int
name string
email string
}
//声明结构体类型的变量
var tom Person
//变量赋值
tom.name = "qy"
tom.age = 19
fmt.Println(tom.name)

匿名结构体

1
2
3
4
5
6
7
8
9
var tom struct {
age int
name string
email string
}
//变量赋值
tom.name = "qy"
tom.age = 19
fmt.Println(tom.name)

结构体初始化

1
2
3
4
5
6
7
tom := Person{
age: 0,
name: "qy",
email: "",
}
// or
tom := Person{0, "qy", ""}

结构体指针

1
2
var p *Person = &tom
fmt.Printf("%p\n", p)

方法

值传递和引用传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Person struct {
name string
age int
}

// 值传递
func (p Person) change1() {
p.name = "z1"
}

// 引用传递(因为编译器对其进行了优化, 所以在struct下简化了很多)
func (p *Person) change2() {
p.name = "z2"
}
func main() {
var p Person
p.name = "l"
fmt.Println(p)
p.change1()
fmt.Println(p)
p.change2()
fmt.Println(p)
}

Golang中的方法都是绑定在指定数据类型上,不一定是struct,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type integer int

func (i integer) myPrint() {
fmt.Println(i)
}

func (i *integer) change() {
*i = 33
}

func main() {
var n integer = 3
n.myPrint()
}

如果一个类型绑定了String()方法,则fmt.Println默认会调用此方法进行输出

方法的访问控制同函数一样,方法首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

继承

要想实现继承,只需要嵌入一个匿名结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Animal struct {
Name string
}

func (animal Animal) introduce() {
fmt.Printf("I'm %s", animal.Name)
}

type Cat struct {
Animal
Miaomiao string
}

func (cat Cat) shot() {
fmt.Println("miao~")
}
func main() {
c := Cat{}
c.Miaomiao = "miao"
c.Name = "sanhua"
c.shot()
c.introduce()
}

错误处理

使用defer+recover机制处理错误

1
2
3
4
5
6
7
8
9
10
11
12
func test() {
defer func() {
err := recover()
//若没有错误, 则返回空值
if err != nil {
fmt.Println(err)
}
}()
a := 1
b := 0
fmt.Println(a / b)
}

自定义错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
err := test()
if err != nil {
fmt.Println(err)
}
}

func test() (err error) {
a := 1
b := 0
if b == 0 {
err = errors.New("除数不能为0")
return
} else {
fmt.Println(a / b)
err = nil
return
}

}

并发

默认情况下,在main线程执行完毕后,所有的协程不管怎样都会终止。

WaitGroup

要想实现等待所有协程结束,可以使用Sleep,或者WaitGroup

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
var wg sync.WaitGroup // 只需要定义

for i := 0; i < 5; i++ {
wg.Add(1) // 启动协程时计数+1
go func(n int) {
defer wg.Done() // 结束协程时计数+1
fmt.Println(n)
}(i)
}
wg.Wait()
}

互斥锁

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
func main() {
var lock sync.Mutex
var wg sync.WaitGroup
var num = 0
for i := 0; i < 100000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
num++
lock.Unlock()
}()
}
for i := 0; i < 100000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
num--
lock.Unlock()
}()
}
wg.Wait()
fmt.Println(num)
}

读写锁:适用于读次数远高于写次数的场景

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
func main() {
var wg sync.WaitGroup
var lock sync.RWMutex
var num = 0
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.Lock()
num++
lock.Unlock()
}()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
lock.RLock()
fmt.Println(num)
lock.RUnlock()
}()
}
wg.Wait()
fmt.Println(num)
}

管道

管道的本质是队列,本身线程安全,不需要加锁

管道是引用类型,必须使用make进行初始化后才能写入数据

管道的示例

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
que := make(chan int, 3)
que <- 1
que <- 2
que <- 3
num := <-que
fmt.Println(num)
num = <-que
fmt.Println(num)
num = <-que
fmt.Println(num)
}

可以使用close关闭管道,但是仍然可以读数据

并发简述

go放到被调用的函数前,函数执行时便会作为一个独立的线程

并发通信

  • 通道Channel

    一个channel只能传递一种类型的值

域套接字

Unix domain sockets,用于在同一主机操作系统上执行的进程之间交换数据。

server.go

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// server.go
package main

import (
"fmt"
"net"
"os"
)

func main() {
// 定义Unix套接字路径
socketPath := "/var/run/mysocket.sock"

// 删除已存在的Unix套接字文件
os.Remove(socketPath)

// 创建Unix套接字监听
listener, err := net.Listen("unix", socketPath)
if err != nil {
fmt.Println("Error listening:", err)
return
}
defer listener.Close()

fmt.Println("Server listening on", socketPath)

// 接收客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting:", err)
return
}
defer conn.Close()

fmt.Println("Server accepted connection")

// 处理客户端发送的数据
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("Error reading:", err)
return
}

fmt.Println("Received message from client:", string(buffer[:n]))
}

client.go

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
32
33
// client.go
package main

import (
"fmt"
"net"
)

func main() {
// 定义Unix套接字路径
socketPath := "/var/run/mysocket.sock"

// 连接到服务器
conn, err := net.Dial("unix", socketPath)
if err != nil {
fmt.Println("Error connecting:", err)
return
}
defer conn.Close()

fmt.Println("Connected to server")

// 发送消息给服务器
message := "Hello, server!"
_, err = conn.Write([]byte(message))
if err != nil {
fmt.Println("Error sending:", err)
return
}

fmt.Println("Message sent to server:", message)
}

连接MongoDB

上下文

  • mongo的任何操作都离不开一个上下文环境

  • contex.background:创建默认的,无限期的上下文

  • context.todo

  • select语句类似switch,只能用于通道操作;select语句会监听所有指定的通道的操作,一旦一个通道准备好就会执行响应的代码块

  • 当select为空的时候,有一个作用是可以防止程序在创建完协程后立即退出

  • defer:常用于资源的释放和异常的捕获,defer后的语句会在return后执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
    return
    }
    dst, err := os.Create(dstName)
    if err != nil {
    return
    }
    dst.Close()
    src.Close()
    return
    }

    当有异常,会直接return,此时资源未释放

  • panic:类似Exception,是用来抛出异常的,recover用来恢复异常,比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    func main() {
    f()
    fmt.Println("Returned normally from f.")
    }

    func f() {
    defer func() {
    if r := recover(); r != nil {
    fmt.Println("Recovered in f", r)
    }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
    }

    func g(i int) {
    fmt.Println("Printing in g", i)
    panic(i)
    fmt.Println("After panic in g", i)
    }

  • 数组:

    1
    var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • time模块

    time.NewTicker:Ticker是周期触发的定时器,可以理解为特殊的通道,每隔一段时间就往这个通道发送时间

  • context:通过实例理解Go标准库context包 | Tony Bai

  • 结构体中若变量类型是指针或slice或map,必须要用make,不然不会分配空间

  • 管道:v, ok := <-ch中的ok代表是否读到数据

  • RPC:

    通俗理解RPC:客户端可以像调用本地程序一样调用远程服务器的应用程序

  • bson:

    bson.D:bson文档的有序表示

    bson.M:bson.M与顺序无关

第三方库

Flag

flag用于解析命令行选项

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
package main

import (
"flag"
"fmt"
)

var (
age int
)

func init() {
fmt.Print("init")
flag.IntVar(&age, "a", 19, "your age")
}
func main() {
fmt.Print("main")
flag.Parse()
fmt.Print(age)
}

/**
C:\Users\28185\Desktop\goLearn>main.exe -a 123
123
*/

值得注意的是,flag.Parse必须在所有选项定义后调用,且flag.Parse后不能定义新的选项.

Msgp

msgp是Message Pack的缩写,是一种二进制序列化格式。

代理

1
2
3
4
5
# 配置代理
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOSUMDB=goproxy.cn/sumdb/sum.golang.org
# 取消代理
go env -u GOPROXY

Go
https://d4wnnn.github.io/2023/01/12/Dev/Go/
作者
D4wn
发布于
2023年1月12日
许可协议