Go 语言上手 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

年过完了,边写论文边学习的节奏也跟上了,Just do it!

一、环境安装(Ubuntu 18.04)

1. 安装包下载

image.png 点击下载 Linux 版本的以 tar.gz 结尾的 Go 二进制安装包。

2. 安装 Go 二进制包

  • 进入安装包所在目录,将下载的二进制包解压至 /usr/local目录。

     tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
     sudo mv go /usr/local/
    
  • 将必要的环境变量添加至 PATH ,编辑 ~/.bashrc 加入下面三行代码。

     export GOROOT=/usr/local/go
     export GOPATH=$HOME/go
     export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
    
  • 刷新环境

     source ~/.bashrc
    
  • Go 1.11 版本开始,Go 提供了 Go Modules 的机制,推荐设置以下环境变量,第三方包的下载将通过国内镜像,避免出现官方网址被屏蔽的问题。

     go env -w GOPROXY=https://goproxy.cn,direct
    

二、基础语法

1. Hello World

  • 新建一个文件 main.go,写入

     package main
     import (
         "fmt"
     )
     func main() {
         fmt.Println("hello world")
     }
    
  • package main:声明了 main.go 所在的包,Go 语言中使用包来组织代码。一般一个文件夹即一个包,包内可以暴露类型或方法供其他包使用。
  • import “fmt”:fmt 是 Go 语言的一个标准库/包,用来处理标准输入输出。
  • func main:main 函数是整个程序的入口,main 函数所在的包名也必须为 main
  • fmt.Println(“Hello World!”):调用 fmt 包的 Println 方法,打印出 “Hello World!”

2. 变量

  • Go 语言与其他语言显著不同的一个地方在于,Go 语言的类型在变量后面

     var a int // 如果没有赋值,默认为0
     var a int = 1 // 声明时赋值
     var a = 1 // 声明时赋值
     ​
     // 自动确定的赋值方式
     // int
     a := 1 
     // string
     msg := "Hello World!"
     // array
     arr := [5]int{1, 2, 3, 4, 5}
     // map
     m2 := map[string]int{
         "Apple": 1,
         "Banana": 2,
     }
     // pointer
     *p = "Hello"
    

3. 简单类型

  • 空值:nil
  • 整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, …
  • 浮点数类型:float32, float64
  • 字节类型:byte (等价于uint8)
  • 字符串类型:string
  • 布尔值类型:boolean,(true 或 false)

4. 流程控制

  • if else 条件语句

     if score := 60; score < 60 {
         ...
     } else {
         ...
     }
    
  • for 循环

     for i := 0; i < 5; i++ {
         ...
     }
    
  • switch 分支选择

     type Score int16
     const (
         NORMAL  Score = 65
         GOOD    Score = 70
     )
     ​
     score := 70
     ​
     switch score {
     case NORMAL:
         fmt.Println("normal")
     case GOOD:
         fmt.Println("good")
     default:
         fmt.Println("low")
     }
    

    if 和 for 语句在判断条件之前能够进行值的初始化!

5. 函数

  • 一个典型的函数定义如下,使用关键字 func,参数可以有多个,返回值也支持有多个。特别地,package main 中的func main() 约定为可执行程序的入口。

     func funcName(param1 Type1, param2 Type2, ...) (return1 Type3, ...) {
         // body
     }
    

6. 结构体和方法

  • 结构体类似于其他语言中的 class,可以在结构体中定义多个字段,为结构体实现方法,实例化等。接下来我们定义一个结构体 Student,并为 Student 添加 name,age 字段,并实现 hello() 方法。

     type Student struct {
         name string
         age  int
     }
     ​
     func (stu *Student) hello(person string) string {
         return fmt.Sprintf("hello %s, I am %s", person, stu.name)
     }
     ​
     func main() {
         stu := &Student{
             name: "Tom",
         }
         msg := stu.hello("Jack")
         fmt.Println(msg) // hello Jack, I am Tom
     }
    

7. 错误处理

  • 示例中,用 r 捕获了能够预知的错误,defer 用来接手出现 panic 以后的控制权
  •  func get(index int) (ret int) {
         defer func() {
             if r := recover(); r != nil {
                 fmt.Println("Some error happened!", r)
                 ret = -1
             }
         }()
         arr := [3]int{2, 3, 4}
         return arr[index]
     }
     ​
     func main() {
         fmt.Println(get(5))
         fmt.Println("finished")
     }
    

8. 接口

  • 不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可。

     type Person interface {
         getName() string
     }
     ​
     type Student struct {
         name string
         age  int
     }
     ​
     func (stu *Student) getName() string {
         return stu.name
     }
     ​
     type Worker struct {
         name   string
         gender string
     }
     ​
     func (w *Worker) getName() string {
         return w.name
     }
    

9. JSON处理

  • go 语言言里面的 JSON 操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用 JSON.marshaler 去序列化,变成一个 JSON 的字符串。序列化之后的字符串也能够用 JSON.unmarshaler 去反序列化到一个空的变是里面。这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用 json tag 等语法去修改输出 JSON 结果里面的字段名。

     package main
     ​
     import (
         "encoding/json"
         "fmt"
     )
     ​
     type userInfo struct {
         Name  string
         Age   int `json:"age"` // 表示可以用首字母小写的‘age’表示这个key
         Hobby []string
     }
     ​
     func main() {
         a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
         buf, err := json.Marshal(a)
         if err != nil {
             panic(err)
         }
         fmt.Println(buf)         // [123 34 78 97...]
         fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
     ​
         buf, err = json.MarshalIndent(a, "", "\t")
         if err != nil {
             panic(err)
         }
         fmt.Println(string(buf))
     ​
         var b userInfo
         err = json.Unmarshal(buf, &b)
         if err != nil {
             panic(err)
         }
         fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
     }
     ​
    

10. 时间处理

  • 在 go 浯言里面最常用的就是 time.now() 来获取当前时间,然后你也可以用 time.date 去构造一个带时区的时间,构造完的时间。上面有很多方法来获取这个时间点的年月日小时分钟秒,然后也能用 sub 去对两个时间进行减法,得到一个时间段。时间段又可以去得到它有多少小时 , 多少分,多少秒。

     now := time.Now()
     fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
     t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
     t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
     fmt.Println(t)   // 2022-03-27 01:25:36 +0000 UTC
     fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
     fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
     diff := t2.Sub(t)
     fmt.Println(diff)                           // 1h5m0s
     fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
    

11. 数字解析

  • go 浯言当中 , 关于字符串和数字类型之间的转都在 strconv 这个包下,用 parselnt 或者 parseFloat 来解析一个字符串 。用 Atoi 把一个十进制字符串转成数字,也可以用 itoA 把数字转成字符串 。

     f, _ := strconv.ParseFloat("1.234", 64)
     fmt.Println(f) // 1.234
     ​
     // 中间的参数10,代表按照10进制数进行解析
     n, _ := strconv.ParseInt("111", 10, 64)
     fmt.Println(n) // 111
     ​
     // 中间的参数0,代表按照规定字符书写前缀的进制数进行解析
     n, _ = strconv.ParseInt("0x1000", 0, 64)
     fmt.Println(n) // 4096
     ​
     n2, _ := strconv.Atoi("123")
     fmt.Println(n2) // 123
     ​
     n2, err := strconv.Atoi("AAA")
     fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
    

12. 进程信息

  • 用 os.argv 来得到程序执行的时候的指定的命令行参数 ,用 os.getenv 来读取环境变量 。

     // go run example/20-env/main.go a b c d
         fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
         fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
         fmt.Println(os.Setenv("AA", "BB")) // 设置环境变量AA为字符串"BB"
     ​
         buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
         if err != nil {
             panic(err)
         }
         fmt.Println(string(buf)) // 127.0.0.1       localhost
    

以上过了一遍 go 的基础,包括变量和语法以及一些必要的内部库用法,基本上能够让我能够看懂 go 代码的基本结构,也有很多小细节和小特性需要注意的地方,比如很多库函数会在第二返回值中返回错误信息,用 "_" 去跳过函数的某个返回值,汉语存储方式等等,需要更精细化的去看 go 语言在实际使用过程中对计算机资源的操作方式,以便能够更好的利用 go 的特性发挥出最大的价值。阅读《Go语言圣经》便是精细化的了解方式。