Go语言入门指南 | 青训营

98 阅读4分钟

青训营课程里,我们需要学习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调用的程序!

指定这个路径的原因是:

  1. 我平常就在Codes下写代码,统一方便管理
  2. 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调用过程是这样的:

  1. 给返回值赋值
  2. 调用defer
  3. return 比如:
func f() (ret int32) {
    defer func() {
        ret++
    }()
    return 0
}

返回1,返回过程是这样的:

  1. 给ret赋值为0
  2. ret递增
  3. 返回

再者说:

func f() (ret int32) {
    v := 1
    defer func() {
        v++
    }()
    return v
}

返回1,过程是这样的

  1. ret = v
  2. v++
  3. 返回
  • 类似面向对象

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会自动管理它。也就是说,几乎没有兼容性问题。当然也有例外,比如某个变量名被选中为关键字了。