青训营课程里,我们需要学习golang。课程的内容很有用,但是在实践中,依然还需要动手解决一些问题,理解其中的一些原理。比如GOPATH、defer之类的。
安装
在Archlinux中,安装直接用pacman -S go即可。
一个Go项目是什么样子的?
官方推荐的做法,是在$GOPATH/src下创建项目进行开发。
虽然我不太理解为什么要这么做,但是至少我知道不这么做是不行的。在执行go mod init的时候总是要指定路径,在自己的项目中导入自己写的代码,编译也不通过,提示找不到包。总之这么做就对了。
当然还有一个奇怪的地方,就是如果我不指定$GOPATH,go语言会自动在$HOME下创建一个go文件夹。有强迫症的我怎么能够容忍这个?
Archlinux中,golang没有自动指定一个$GOPATH,所以这需要自己指定。由于我使用的是zshell,打开~/.zshrc写入如下内容:
export GOPATH="$HOME/Codes/golang"
注意,export不能缺少!它会将环境变量传递给所有由shell调用的程序!
指定这个路径的原因是:
- 我平常就在Codes下写代码,统一方便管理
- golang既然需要一个GOPATH,那就写一个到环境变量算了
基本语法
想必读者是学习过其他语言的,所以我更加偏向使用对比的方式来介绍golang的基本语法。
- 定义变量:
定义变量的方式有很多,仅简单说明。
var v int // 1. 类型放在后面
w := 1 // 2. 直接赋值,使用:=运算符
var (
a int
b int
) // 3. 连续定义变量
type T struct {
a int
b int
} // 4. 定义一个结构体
type I interface {
Method(args)
} // 5. 定义一个接口
内置的一些变量:
var str string
mp := map[string]string
arr := []int{1, 2, 3}
nums := [5]int
定义数组时,长度不能够是变量。如果要变长数组,要使用slice
arr := make([]int, 1)
arr = append(arr, 34)
- 流程控制
if 语句,其中初始化可以省略。这样的语法也在新版的C++中引入了:
if 初始化; 条件表达式 {
...
} else if 条件表达式 {
...
} else {
...
}
循环语句。golang中只有for循环,但是却有着不同的写法。也就是说,一个for,代替了for,for-each,while三种循环。
for 初始化; 条件; 表达式 {
}
for i, v := range 可迭代遍历的变量 {
}
for 条件 {
}
Golang的特色
- defer
defer定义在函数执行完的时候才执行的语句。
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
但是要注意的是,return不是原子命令!真正的return调用过程是这样的:
- 给返回值赋值
- 调用defer
- return 比如:
func f() (ret int32) {
defer func() {
ret++
}()
return 0
}
返回1,返回过程是这样的:
- 给ret赋值为0
- ret递增
- 返回
再者说:
func f() (ret int32) {
v := 1
defer func() {
v++
}()
return v
}
返回1,过程是这样的
- ret = v
- v++
- 返回
- 类似面向对象
golang中没有面向对象,也没有封装继承。但是却有着结构体变量.方法名()的用法
要修改结构体值的时候,用这个写法
func (t *T)SetA(v int) {
t.a := v
}
不需要修改的时候,用这个写法
func (t T)GetA() (int) {
return t.a
}
golang没有继承,但是有interface。
type FeedResponse {
StatusCode int32
...
}
func (r *FeedResponse)SetStatusCode(code int32) {
r.StatusCode = code
}
type CommentResponse {
StatusCode int32
...
}
func (r *CommentResponse)SetStatusCode(code int32) {
r.StatusCode = code
}
type BaseResp interface {
SetStatusCode(int32)
}
func BuildBaseResp(errno int32, resp BaseResp) {
resp.SetStatusCode(errno)
}
然后在调用的时候,可以这么写,值得注意的是,必须传入指针
fr := FeedResponse{}
BuildBaseResp(10, &fr)
当然还可以这样
var cr BaseResp
cr = new(CommentResponse{})
BuildBaseResp(111, cr)
- 错误处理:
在Java、C++、Python中,有try-catch这样的语法,但是在golang里,没有,所以一般是第一个返回值当作需要的值,第二个是错误信息。错误信息的类型是error,它也是一个interface。
resp, err := RPCCall(1)
if err != nil {
输出错误信息
}
使用resp
还有一种方式,当程序出现严重错误的时候,调用panic()。当然,如果这个严重错误是别的包中出现的,对于你写的代码而言,这个错误不影响,可以使用recover跳过该错误。但是这始终不是try-catch,倒是有些像C语言的setjump
- 严格的语法检查
如果你创建了一个变量但是没有使用它,那么编译器会报错
- 自动版本管理
工程中,不同人写的不同的包,调用的库是不一样的。golang会自动管理它。也就是说,几乎没有兼容性问题。当然也有例外,比如某个变量名被选中为关键字了。