1. 安装
1.1 本体软件
在mac下,使用brew install go进行安装。另外,我们知道从 Go 1.11 版本开始,官方支持了 go module 包依赖管理工具(参见 https://www.cnblogs.com/sage-blog/p/10640947.html),并新增了 GOPROXY 环境变量,下载源代码时将会通过这个环境变量设置的代理地址,而不再是以前的直接从代码库下载。goproxy.io 这个开源项目帮我们实现好了我们想要的。该项目允许开发者一键构建自己的 GOPROXY 代理服务。同时,也提供了公用的代理服务 https://goproxy.io,我们只需设置该环境变量即可正常下载被墙的源码包了:
# 启用 Go Modules 功能
export GO111MODULE=on
# 配置 GOPROXY 环境变量
export GOPROXY=https://goproxy.io
1.2 与jupyter交互
参见 https://github.com/gopherdata/gophernotes,在mac下的安装命令为
env GO111MODULE=on go get github.com/gopherdata/gophernotes
mkdir -p ~/Library/Jupyter/kernels/gophernotes
cd ~/Library/Jupyter/kernels/gophernotes
cp "$(go env GOPATH)"/pkg/mod/github.com/gopherdata/gophernotes@v0.7.0/kernel/* "."
chmod +w ./kernel.json # in case copied kernel.json has no write permission
sed "s|gophernotes|$(go env GOPATH)/bin/gophernotes|" < kernel.json.in > kernel.json
安装好之后,打开jupyter notebook就能看到新增了go内核啦。
1.3 编程规范
和python一样,Go是讲究书写格式的,函数的第一个{一定要和函数体在一起。有如下几个包可以控制格式
- 使用gofmt -w命令标准化go文件
- 使用golint给出修改建议
- 使用godoc生成文档
1.4 运行
使用go get安装github上的包。-D表示仅下载不安装。使用go install将src里面的源文件安装到$GOBIN文件夹下。
源文件使用go build编译、go run运行。也可以直接go run XX.go运行,不要太方便。
另一种方式是使用gopm。在nodejs中我们有npm,可以通过npm来下载安装一些依赖包。在go中也开发了类似的东西,那就是gopm。
使用go get -v -u github.com/gpmgo/gopm进行安装。
它不仅提供go的各种包管理,使用gopm get -g代替go get在国内还有加速效果。
注意不采用-g参数,会把依赖包下载.vendor目录下面;采用-g 参数,可以把依赖包下载到GOPATH目录中。
# 查看当前工程依赖
gopm list
# 显示依赖详细信息
gopm list -v
# 列出文件依赖
gopm list -t [file]
# 拉取依赖到缓存目录
gopm get -r xxx
# 仅下载当前指定的包
gopm get -d xxx
# 拉取依赖到$GOPATH
gopm get -g xxx
# 检查更新所有包
gopm get -u xxx
# 拉取到当前所在目录
gopm get -l xxx
# 运行当前目录程序
gopm run
# 生成当前工程的 gopmfile 文件用于包管理
gopm gen -v
# 根据当前项目 gopmfile 链接依赖并执行 go install
gopm install -v
# 更新当前依赖
gopm update -v
# 清理临时文件
gopm clean
# 编译到当前目录
gopm bin
2. 数据类型
2.1 基本类型
Go是静态语言,数据类型包括:bool,string,int,float32,float64, [](数组)。Go中的变量声明后,默认初始化为“零值”。注意不像python,字符串不能用单引号。如果用反引号`,则不需要做转义。
- 使用reflect包查看数据类型,reflect.TypeOf()
- 使用strconv包进行类型转换,strconv.ParseBool(), strconv.FormatBool…
2.2 数组/切片/字典
- 数组,固定大小,一般不用:
var xx [2]string;xx := []int{1,2,3,4};xx := [6]int{1,2,3,4} - 切片,大小只是个提示,可随时新增:
var xx = make([]string,2)
xx = append(xx,value) - 数组和切片复制,使用:
copy(to, from) - 字典,使用make(map…),复杂数据结构都要用make关键字
xx := make(map[string] int)
用delete删除数组元素:
detele(xx,key)
2.3 常量/变量,声明/赋值,作用域
用const声明常量。
定义变量的几种方式:
var s string
var s string = "hello"
var s = "hello" // 类型推断
s := "hello" // 类型推断+去掉var的简写,只能在函数内使用。
使用块表明作用域,用{}。文件本身是一个块。外部块可以访问内部块。
Go默认从package main的main函数进行。
2.4 结构体
定义:type xx struct{key1 string, key2 int}
使用:
m := xx{
key1: value1
key2: value2
}
或者
m:=xx{value1,value2}
结构体与后面的方法组合起来,可以模拟类。
3. 函数与接口
3.1 指针
和java大同小异:
- 使用 &变量 来获得变量的指针
- 使用 *数据类型 来声明指针类型
- 使用 *指针 来获取指针对应的变量
3.2 函数
func XX(输入 输入格式) (输出格式){
return 输出
}
在输入框中,可以用…表示任意数量的变量
在Go中,可以将函数名放到前面作为变量:
XX:=func (输入 输入格式) (输出格式){
return 输出
}
即可以将 func (输入 输入格式) (输出格式) 作为一种数据类型进行传输,后面加一个()就可以执行一次这个函数变量,非常像java中的内联函数。
3.3 方法
方法非常像函数,只是多了一个接收者。之所以这么定义,是为了模拟面向对象的类,使用type和方法就可以模拟类。
方法定义:func (m *Movie) summary(xx int) string{}
调用:m := Movie{…}; m.summary()
3.4 接口
接口定义:type XX interface{PowerOn() string},定义了接口名、需实现的方法等。
然后因为函数并没有implement关键字,所以实现接口的时候看不出来implement的是谁,最好加注释说明。
4. 控制语句
- 条件语句:if x {} else {},可以继续else if下去
- 循环语句1,经典for循环:for i:=0;i<10;i++{}
- 循环语句2,使用range,很像python中的enumerate:
numbers := []int{1,2,3,4}
for i,n := range numbers{
fmt.Println(n)
}
- 没有while语句,用for代替
for {这里面的语句会不停执行,要自己加停止条件}
- defer:函数执行完之后会执行defer语句,在关闭网络连接、关闭文件等作业中很有用。
5. 读写/测试/日志/异常/建站
- 读写文件:使用io/ioutil,读出来是[]unit8,需转换成需要的形式。注意缓存读写buffer.Bytes处理字符并直接使用string的+和append方法要快(可使用benchmark测试)。
- json:用encoding/json包处理json字段
- 正则表达式:regexp包
- 异常处理:使用errors,其接口定义:type error interface{Error() string}。fmt.Errorf对错误做格式化输出
- 日志:使用log,log.Printf、log.Fatal…。一般直接打印到标准输出,然后在程序外处理。标准输入0;标准输出1;标准错误2
- 获取命令行参数:使用range os.Args获取所有参数;或者用flag.String(参数名,默认值,提示)定义,再使用flag.Parse()解析,使用*参数名获取值。
- 测试:使用testing,参考这里。几个关键点:(1) 用xx_test.go命名测试文件;(2) 引入import “testing”;(3) 测试函数以Test开头。有两类测试,t表示单元测试,b表示基准测试。使用 -bench运行基准测试,使用-cover 报告覆盖率。
- 建站:使用net/http包,下面是例子:
package main
import "net/http"
func hello(w http.ResponseWriter, r *http.Request){
w.Write([]byte("Hello world"))
}
func main(){
http.HandleFunc("/",hello)
http.ListenAndServe(":8000",nil)
}
6. 并发和通道
- 在函数前加一个go即可触发并发,一般配合通道chan一起执行。通道定义方法:c:=make(chan string)。当通道作为参数时:<-chan表明只读,chan<-表明只写,chan表明可读写
- 通道接收消息:c <- “hello”;接收通道数据:xx := <-c,这是一个阻塞操作
- 高级使用方法:用select case语句可以执行第一条达到的语句,执行后立即取消阻塞。使用time.After设置超时,格式为:
//select基本用法
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
case <-time.After(5*time.Second):
// 如果上面都没有成功,则进入default处理流程。或者使用default执行默认操作
下面是例子
func showfunc(c chan string){
t := time.NewTicker(time.Second*2)
for {
c <- "function finished"
<-t.C
}
}
func main(){
c:=make(chan string)
stop:=make(chan bool)
go showfunc(c)
go func() {time.Sleep(time.Second*4);stop <- true}() //使用内联函数
fmt.Println("start immediately")
for {
select{
case <-stop:
return
case msg := <-c:
fmt.Println(msg)
}
}
}
main()