Go 学习笔记

Go 环境搭建

  1. 安装 Go。Mac 可以用 brew 安装 Go : brew install go。Windows 直接下载安装包安装就行。在命令行下运行 go version 命令后能输出版本号表示安装成功。

  2. VS Code 安装 Go 插件

环境安装过程中的坑

Go 插件有可能安装失败

解决办法:

1
2
3
4
5
// 开启代理设置
go env -w GO111MODULE=on

// 设置代理
go env -w GOPROXY=https://goproxy.io,direct

注意:开启代理设置前,可以使用 go env 命令查看是否已经默认开启了代理设置,如果已经开启就不需要在再次开启了,直接设置代理就行。

package main 语句报错

错误信息:

1
go: cannot find main module; see 'go help modules'

以及:

1
The code in the workspace failed to compile (see the error message below). If you believe this is a mistake, please file an issue: https://github.com/golang/go/issues/new. go [-e -json -compiled=true -test=true -export=false -deps=true -find=false -- ./]: exit status 1: go: cannot find main module; see 'go help modules' : packages.Load error

上面的问题都是由于 Go 的 Modules 导致的。
解决方法:

  1. 禁用 Modules(不推荐)。运行 go env -w GO111MODULE=off
  2. 使用 go.mod。在项目的根目录运行 go mod init main (其中 main 是模块名)

VS Code 没有代码提示

没有代码提示也是因为 go.mod 文件导致的。解决办法也是在项目的根目录运行 go mod init main (其中 main 是模块名)

基本结构和基本数据类型

文件名、关键词与标识符

  • 文件名以 .go 为扩展名,文件名均由小写字母组成,单词间以 _ 分隔。文件名不包含空格或其他特殊字符。
  • Go 语言也是区分大小写的。
  • 有效的标识符必须以字符(可以使用任何 UTF-8 编码的字符或 _)开头。
  • _ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。在编码过程中,你可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。
  • 代码不用显示以 ; 结尾,因为 Go 编译器自动完成这项工作。
  • 如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

Go 程序的基本结构和要素

示例

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

import "fmt"
import "os"

//或者
import "fmt"; import "os" // 不推荐

// 或者
import (
"fmt"
"os"
)

// 或者
import ("fmt"; "os")

包的路径

  • 包名不是以 ./ 开头,则 Go 会在全局文件进行查找;
  • 包名以 ./ 开头,则 Go 会在相对目录中查找;
  • 如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
  • 相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。

包中标识符的可见性

  • 以大写字母开头的标识符(包括常量、变量、类型、函数名、结构字段等等)对象可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 以小写字母开头的标识符对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private)。
  • 大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母

注意事项

如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有不必要的代码!“。

函数

一般形式

1
2
3
func functionName(parameter_list) (return_value_list) {

}

其中:

  • parameter_list 的形式为 (param1 type1, param2 type2, …)
  • return_value_list 的形式为 (ret1 type1, ret2 type2, …)

注意事项

  • 左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会报错;
  • 右大括号 } 需要被放在紧接着函数体的下一行。如果你的函数非常简短,也可以整个写成一行。
  • Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成

函数的命名

  • 遵循 Pascal 命名法(所有单词首字母大写)的函数可以被外部包调用;
  • 遵循骆驼命名法(第一个单词的首字母小写,其余单词的首字母大写)不能被外部包调用;

main 函数。

  • main() 函数是每一个可执行程序所必须包含的,否则会引发构建错误;
  • 一般来说都是在启动后第一个执行的函数(如果有 init () 函数则会先执行该函数)。
  • main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误。

注释

  • 注释不会被编译,但可以通过 godoc 来使用
  • // 单行注释,是最常见的注释形式。
  • /* ... */ 多行注释(也叫块注释)。不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

类型

  • 使用 var 声明的变量的值会自动初始化为该类型的零值
  • 基本类型,如:int、float、bool、string;
  • 结构化的(复合的),如:struct、array、slice、map、channel;
  • 只描述类型的行为的,如:interface。
  • 结构化的类型没有真正的值,它使用 nil 作为默认值
  • Go 语言中不存在类型继承
  • 多返回值函数:
    1
    2
    3
    func FunctionName (a typea, b typeb) (t1 type1, t2 type2) {
    return var1, var2
    }
  • 使用 type 关键字可以定义你自己的类型
  • Go 语言是一种静态类型语言

Go 程序的一般结构

  1. 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。
  2. 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。
  3. 如果当前包是 main 包,则定义 main 函数。
  4. 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。
    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
    // gotemplate.go
    package main

    import (
    "fmt"
    )

    const c = "C"

    var v int = 5

    type T struct{}

    func init() { // initialization of package
    }

    func main() {
    var a int
    Func1()
    // ...
    fmt.Println(a)
    }

    func (t T) Method1() {
    //...
    }

    func Func1() { // exported function Func1
    //...
    }

Go 程序执行顺序

Go 程序的执行(程序启动)顺序如下:

  1. 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
  2. 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
  3. 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
  4. 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。

类型转换

Go 语言不存在隐式类型转换,所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):

1
valueOfTypeB = typeB(valueOfTypeA)

Go 命名规范

  • 名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符
  • 返回某个对象的函数或方法的名称一般都是使用名词,没有 GetXXX() 之类的字符,如果是用于修改某个对象,则使用 SetXXX()
  • 有必须要的话可以使用大小写混合的方式,如 MixedCapsmixedCaps,而不是使用下划线来分割多个名称。