Golang

Posted by lanbery on July 8, 2019

Golang 基础用法


Go Commands help

go get

go get -v -x github.com/hyper-carrot/go_lib/logging

  • [go get -d] 让命令程序只执行下载动作,而不执行安装动作。
  • [-f] 仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。如果下载并安装的代码包所属的项目是你从别人那里Fork过来的,那么这样做就尤为重要了。
  • [-fix] 让命令程序在下载代码包后先执行修正动作,而后再进行编译和安装。
  • [-insecure]允许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。如果你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,可以添加此标记。请在确定安全的情况下使用它。
  • [-t]让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
  • [-u]让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
  • [-x] 命令时也可以加入-x标记,这样可以看到go get命令执行过程中所使用的所有命令

解决傻X问题

export http_proxy=http://localhost:9527 export https_proxy=http://localhost:9527

如果ss5

export ALL_PROXY=socks5://127.0.0.1:1080

GOPROXY 环境变量

终于到了本文的终极大杀器 —— GOPROXY。

我们知道从 Go 1.11 版本开始,官方支持了 go module 包依赖管理工具。

其实还新增了 GOPROXY 环境变量。如果设置了该变量,下载源代码时将会通过这个环境变量设置的代理地址,而不再是以前的直接从代码库下载。这无疑对我等无法科学上网的开发良民来说是最大的福音。

更可喜的是,goproxy.io 这个开源项目帮我们实现好了我们想要的。该项目允许开发者一键构建自己的 GOPROXY 代理服务。同时,也提供了公用的代理服务 https://goproxy.io,我们只需设置该环境变量即可正常下载被墙的源码包了:

export GOPROXY=https://goproxy.io

不过,需要依赖于 go module 功能。可通过 export GO111MODULE=on 开启 MODULE。

如果项目不在 GOPATH 中,则无法使用 go get …,但可以使用 go mod … 相关命令。

也可以通过置空这个环境变量来关闭,export GOPROXY=。

对于 Windows 用户,可以在 PowerShell 中设置:

$env:GOPROXY = “https://goproxy.io”

最后,我们当然推荐使用 GOPROXY 这个环境变量的解决方式,前提是 Go version >= 1.11。

Golang 1.13 后的 module 管理及用法

# 初始化项目mod 模块
cd <project dir> && go mod init 

# 下载模块到本地缓存,缓存路径是 $GOPATH/pkg/mod/cache
go mod download

# 是提供了命令版编辑 go.mod 的功能 -fmt 会格式化 go.mod
go mod edit -fmt

# 增加缺失的包,移除没用的包
go mod tidy

# 把依赖拷贝到 vendor/ 目录下[即项目下vendor]
go mod vendor

# 确认依赖关系
go mod verify 

# 把模块之间的依赖图显示出来
go mod graph

# 解释为什么需要包和模块
go mod why

go mod 指令

  • module : 声明module名称
  • require : 声明依赖及其版本号(该指令告诉 go build 使用指定版本包来编译)
  • replace : 替换require声明的依赖,使用外部的依赖及其版本号
  • exclude : 禁用指定的依赖

replace 工作机制

如果我们想使用protobuf的v1.1.0版本进行构建,可以修改require指定的版本号,还可以使用replace来指定

正常情况下是不需要用replace的,这不是它的使用场景,下面会有使用场景

module github.com/jk/test

go 1.16

require google.golang.org/protobuf v1.1.1
replace google.golang.org/protobuf v1.1.1 => google.golang.org/protobuf v1.1.0

# 此时编译时就会选择v1.1.0 版本,如果没有自动下载

replace 的使用场景

  • 替换无法下载的包

例如沦陷区网络问题,有些包无法下载:比如golang.org但是可以从 github.com clone 下来

replace (
  golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2
)

Go 基础语法

go 关键字

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

slice

slice 是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的 aSlice 和 bSlice,如果修改了 aSlice 中元素的值,那么 bSlice 相对应的值也会改变。

// 声明一个数组
var array = [10]byte{'a','b','c','d','e','f','g','h','i','j'}

//声明两个slice
var aSlice,bSlice []byte

//一些操作
aSlice = array[:3] // 等价于 aSlice = array[0:3] a,b,c
aSlice = array[5:] // 等价于 aSlice = array[5:10]
aSlice = array[:] 

// 从slice 取
aSlice = array[3:7]   // aSlice 包含元素:d,e,f,g  len=4 cap=7
bSlice = aSlice[1:3]  // bSlice 包含aSlice[1],aSlice[2] :e,f
bSlice = aSlice[:3]   // aSlice[0],aSlice[1],aSlice[2] : d,e,f
bSlice = aSlice[0:5]  // 对slice 的slice 可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h

slice 内置函数

  • len 获取slice 长度
  • cap 获取slice的最大容量
  • append 向slice里面追加一个或多个元素,然后返回一个和slice一样类型的slice
  • copy 从源slice 的src 中复制元素到目标 dest,并且返回复制的元素个数

append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。 但当 slice 中没有剩余空间(即 (cap-len) == 0 )时,此时将动态分配新的数组空间。返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响


map

map 也就是 Python 中字典的概念,它的格式为 map[keyType]valueType

我们看下面的代码,map 的读取和设置也类似 slice 一样,通过 key 来操作,只是 slice 的 index 只能是 int 类型,而 map 多了很多类型,可以是 int,可以是 string 及所有完全定义了 == 与 != 操作的类型


// 声明一个 key 是字符串,值为 int 的字典, 这种方式的声明需要在使用之前使用 make 初始化
var numbers map[string]int
// 另一种 map 的声明方式
numbers := make(map[string]int)
numbers["one"] = 1  // 赋值
numbers["ten"] = 10 // 赋值
numbers["three"] = 3

fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3

make,new 操作

make 用于内建类型(map、slice 和 channel)的内存分配。new 用于各种类型的内存分配。

内建函数 new 本质上说跟其它语言中的同名函数功能一样:new(T) 分配了零值填充的 T 类型的内存空间,并且返回其地址,即一个 *T 类型的值。用 Go 的术语说,它返回了一个指针,指向新分配的类型 T 的零值.

make 返回初始化后的(非零)值

  • 下图解释了new & make 之间的区别

零值

int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 // rune 的实际类型是 int32
byte    0x0 // byte 的实际类型是 uint8
float32 0 // 长度为 4 byte
float64 0 // 长度为 8 byte
bool    false
string  ""

for 循环

// 可以使用 _ 来丢弃不需要的返回值
for _,v := range map {
  fmt.Println("Map's val:",v)
}


Panic 和 Recover

Go 没有像 Java 那样的异常机制,它不能抛出异常,而是使用了 panic 和 recover 机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有 panic 的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢

Panic

是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数 F 调用 panic,函数 F 的执行被中断,但是 F 中的延迟函数会正常执行,然后 F 返回到调用它的地方。在调用的地方,F 的行为就像调用了 panic。这一过程继续向上,直到发生 panic 的 goroutine 中所有调用的函数返回,此时程序退出。恐慌可以直接调用 panic 产生。也可以由运行时错误产生,例如访问越界的数组。

Recover

是一个内建的函数,可以让进入令人恐慌的流程中的 goroutine 恢复过来。recover 仅在延迟函数中有效。在正常的执行过程中,调用 recover 会返回 nil,并且没有其它任何效果。如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。

  • code

var user = os.Getenv('USER')

func init() {
  if user == "" {
    panic("no value for $USER")
  }
}


func throwsPanic(f func()) (b bool) {
  defer func() {
    if x := recover(); x != nil {
      b = true
    }
  }()

  f() // 执行函数发,如果f中出现了panic,那么就可以恢复回来

  return
}

main 函数 & init 函数

Go 里面有两个保留的函数:init 函数(能够应用于所有的 package )和 main 函数(只能应用于 package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个 package 里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个 package 中每个文件只写一个 init 函数。

Go 程序会自动调用 init() 和 main(),所以你不需要在任何地方调用这两个函数。每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。

程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。下图详细地解释了整个执行过程