Go 语言上手-基础语言 | 青训营笔记

218 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

1 简介

1.1 什么是Go语言

  • Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
  • Go语言的特点有:
      1. 高性能、高并发:性能可以和C++、Java媲美,标准库或基于标准库的第三方库配置了许多对高并发的支持,不用再去寻找专门支持高并发的第三方库
      1. 语法简单:语法类似C语言,且进行了大量简化,学习时间较短
      1. 丰富的标准库:支持大部分基础功能的开发,稳定性和兼容性很高,语言迭代也会有性能优化
      1. 完善的工具链:拥有对应的编译、代码格式化、错误检查、包管理、代码补充提示、单元测试等工具
      1. 静态链接:体积可以控制的很小,部署方便而且快捷
      1. 快速编译:编译速度很快,本地开发修改代码后可以很快完成增量编译
      1. 跨平台:可以在Windows、Linux、Mac OS等环境下运行,也可以开发安卓、iOS软件,还可以在路由器、树莓派等设备上运行,且有交叉编译特性
      1. 垃圾回收:无需考虑内存分配释放,可以专注业务逻辑

2 入门

2.1 开发环境 - 安装Golang

可以在 go.dev/、https://st…

Golang的集成开发环境有VSCode和Goland,VSCode是一款编辑器,需要下载相应的插件完成对go开发环境的配置,Goland是一款IDE,可以直接使用进行代码开发。

2.2 基础语法

2.2.1 变量

  • 变量声明的两种方式:
    • 一种是通过var关键字的方式去声明变量,比如var a = 1var a int = 1,当不写出类型名时,会自动推导变量的类型。
    • 另一种是通过:=的方式去声明变量,比如a := 1,这种方式既进行了声明又进行了初始化,要求变量不能之前已经声明过了,会自动进行类型推导。
    • 如果是声明常量,则直接使用const关键字,比如const a = 1,类型,类型会自动确定

2.2.2 if else

  • if else的语法和C类似,都有if、else if和else,但是有两点不同:
    • if后面可以没有括号,即使写上括号,最后也会自动去掉
    • if后面必须接大括号,而且不能将条件判断的语句写在同一行里

2.2.3 循环

  • go里面只有for循环,没有while或者do while循环
  • 循环里面,可以用break跳出循环,或者用continue继续循环

2.2.4 switch

  • go里面的switch和C比较类型,但也有一些不同:
    • switch后面要进行判断的那个变量,不用加上括号
    • case里面的语句最后可以不用加上break,只会执行这一个case,而不会一直向下执行
    • switch后面要进行判断的那个变量可以是任意类型,也可以不加变量,在case里写条件分支,模拟if else语句

2.2.5 数组

  • 数组声明:var a[5] intvar a[3][4] int
  • 数组初始化:b := [5]int{1, 2, 3, 4, 5}
  • 数组长度是固定的,在真实业务代码中,更常使用的是切片

2.2.6 切片

  • 切片可以任意修改长度,且可以像数组一样使用下标进取值
  • 可以使用make来创建一个切片:s := make([]string, 3)
  • 可以使用append来添加元素,append的结果要赋值为原数组,因为有可能发生扩容,此时得到的是一个新的切片:s = append(s, "d")
  • 切片的操作和python类似,可以使用:取出指定位置的元素,比如s[2:5]代表取出s中的第二个到第四个元素,左闭右开,但不支持负数索引

2.2.7 map

  • 可以使用make来创建空的map:m := make(map[string]int)
  • 存储键值对:m["one"] = 1,删除键值对:delete(m, "one")
  • go里的map是完全无序的,遍历的时候是按随机顺序

2.8 range

  • range可以用来快速遍历一个slice或一个map,遍历时会返回两个值,一个是索引,一个是对应位置的值,不需要索引的话,可以使用下划线来进行忽略,示例:
    nums := []int{2, 3, 4}
    for i, num := range nums {
        if num == 2 {
            fmt.Println("index:", i, "num:", num) // index: 0 num: 2
        }
    }
    

2.9 函数

  • go的变量类型是后置的,支持返回多个值,在实际的业务逻辑代码中几乎所有的函数都返回两个值,第一个是结果,第二个是错误的信息,示例:
    func add(a int, b int) int {
        return a + b
    }
    
    func exists(m map[string]string, k string) (v string, ok bool) {
        v, ok = m[k]
        return v, ok
    }
    

2.10 指针

  • go里的指针和C中的类似,但是支持的操作比较有限,主要功能是对传入参数进行修改,示例:
    func add2ptr(n *int) {
        *n += 2
    }
    
    func main() {
        n := 5
        add2ptr(&n)
        fmt.Println(n) // 7
    }
    

2.11 结构体和结构体方法

  • 结构体是带类型的字段的集合,可以使用结构体的名称去初始化一个结构体变量
  • 初始化构造的时候需要传入一部分字段的初始值,也可以用键值对的方式去指定初始值
  • 结构体方法的具体代码,就是将这个结构体的一个普通变量或指针变量加上括号写到函数名称的前面,带上指针的变量才可以对结构体进行修改,示例:
    type user struct {
        name     string
        password string
    }
    
    func (u *user) resetPassword(password string) {
        u.password = password
    }
    
    func main() {
        a := user{name: "wang", password: "1024"}
        b := user{"wang", "1024"}
        var d user
        d.name = "wang"
        d.password = "1024"
    }
    

2.12 错误处理

  • 在go里面,习惯使用一个单独的返回值来传递错误信息,使用的标准库为errors
  • 可以在函数的返回值类型后面,加上一个error,代表这个函数可能返回错误
  • 实现函数时,如果没有出现错误,返回原本结果和nil,出现错误返回nil和一个error,示例:
    type user struct {
        name     string
        password string
    }
    
    func findUser(users []user, name string) (v *user, err error) {
        for _, u := range users {
            if u.name == name {
                return &u, nil
            }
        }
        return nil, errors.New("not found")
    }
    
    func main() {
        if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
            fmt.Println(err) // not found
            return
        } else {
            fmt.Println(u.name)
        }
    }
    

2.13 字符串操作和字符串格式化

  • 在标准库strings包中有很多字符串工具函数
  • 在标准库fmt包中有很多字符串格式相关的方法:
    • 可以使用%v来打印任意类型的变量
    • 可以使用%+v打印详细结果,使用%#v打印更详细的结果

2.14 JSON处理

  • JSON处理的标准库是encoding/json
  • 对于一个已有的结构体,只要保证每个字段的第一个字母是大写字母,就可以使用JSON.marshaler方法去序列化这个结构体,使这个结构体变成一个JSON的字符串
  • 序列化之后的字符串也可以使用JSON.unmarshaler方法去反序列化到一个空的变量
  • 可以在结构体变量的后面用json tag等语法来修改输出JSON结果里面的字段名,示例:
    type userInfo struct {
        Name  string
        Age   int `json:"age"`
        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"}}
    }
    

2.15 时间处理

  • 时间处理的标准库是time
  • 可以使用time.Now()来获取当前时间,使用time.Date方法去构造一个带时区的时间
  • 可以使用Unix()方法来获取时间戳,示例:
    func main() {
        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
        t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
        if err != nil {
            panic(err)
        }
        fmt.Println(t3 == t)    // true
        fmt.Println(now.Unix()) // 1648738080
    }
    

2.16 数字解析

  • 关于字符串和数字类型之间转换的方法都放在标准库strconv包下
  • 可以使用parseInt方法或parseFloat方法来解析一个字符串
  • 可以使用Atoi方法把一个十进制字符串转换成数字,可以使用itoA方法把数字转成字符串,示例:
    func main() {
        f, _ := strconv.ParseFloat("1.234", 64)
        fmt.Println(f) // 1.234
        n, _ := strconv.ParseInt("111", 10, 64)
        fmt.Println(n) // 111
        n2, _ := strconv.Atoi("123")
        fmt.Println(n2) // 123
    }
    

2.17 进程信息

  • 可以使用os.Args来获取程序执行时指定的命令行参数
  • 可以使用os.Getenv来获取环境变量
  • 获取进程信息的代码示例:
    import (
        "fmt"
        "os"
        "os/exec"
    )
    
    func main() {
        // 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"))
    
        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
    }
    

3 个人思考

Go语言是一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言,语法比较简单,也有许多对于高并发的支持,因为和C的语法比较类似,所以入门基本语法比较快速,可以很快的看懂源码的逻辑和功能,是一门功能强大,对新手比较友好的语言。