Go快速上手之基础语法 | 青训营笔记

136 阅读8分钟

Go快速上手之基础语法 | 青训营笔记

系列介绍

哈哈哈,其实这个系列如题,就是黄同学也参加了这一届青训营😚。作为一名知识内容输出爱好者,我是非常喜欢这个活动的。在接下来的日子里我会持续更新这个系列,希望可以通过这个过程,将我在这次后端青训营的学习过程,尤其是针对课程中知识内容的课下实践,自己的心得体会,知识总结输出到掘金社区。💖

哈哈哈,其实也希望通过这个来对我这种懒狗的一种鞭策。🏅

本文摘要

  1. 本文旨在介绍Go语言的特性,配置,以及一些基础语法⚡️。
  2. 在介绍这些的时候,会结合我这个计算机小白在实践的过程中遇到的一些问题与个人理解🙂。

1. Go 介绍

事先说明,黄同学在这次青训营之前并没有任何Go语言相关的经历,唯一算有的,可能就是闲的时候看过几篇Go相关的文章,如果有写得不对的地方,希望大家能够指出 🌹。

  1. Go的另外一个名字是 Golang,是由 Google 支持的一种编程语言。在我过去的认知中,Go主要用于服务端的开发。

  2. 特点也可以说是优点:

    1. 高性能高并发:这个应该是 Go 在后端领域应用的一个非常主要的原因吧。根据我查到的一些资料发现Go有提供一套原生的并发机制,这种机制有两个概念 goroutinechannel。这个以后有机会深入研究然后发出来和大家一同讨论,哈哈哈,希望不会🕊。
    2. 语法简单,容易上手。这个是真的,确实语法很简单,不过对于一个比较习惯使用CC++ 的我来说,Go语法,尤其是变量初始化类型在后面这一点确实令我不是很能适应,不过也有通过 := 这种来初始化的方式让我比较好接受。
    3. 开源,社区庞大,丰富的标准库和工具。
    4. 支持静态链接‼️,这一点使得Go程序更容易部署与运行,提高程序的安全与性能。
    5. JAVA类似,具有垃圾回收机制与跨平台特点,相比像C等需要开发者管理内存,要更方便。
  3. 缺点嘛,这里就不谈了,毕竟我用的也比较少,主要是从面向对象编程来看,缺少了向继承多态这些特性。

2. Go 的环境配置

  1. 个人理解所有的编程的学习和工作的最初开展都是环境配置,很多人其实都非常头疼这一步,诸如我们在过去配置C++JavaPython等的环境配置,很多时候都比较复杂。但是,幸运的是,Go的环境配置很简单😄。

  2. Go语言环境下载,主要有下列途径:

    1. All releases - The Go Programming Language,这是Go官网,根据你的OS版本,选择下载。黄同学是windos版本,所以选择的是windows。这个途径也许需要你会一点魔法🔯,如果你不会,你可以选择面两种方式。在这里插入图片描述
    2. Go下载 - Go语言中文网 - Golang中文社区 (studygolang.com)
    3. 七牛云 - Goproxy.cn,七牛云做的国内Go模块代理。
  3. 安装流程,这里不给出图片,当时安装的时候并没有录制,不想重新配一遍(早知道当时就记录保存下来😅 )。具体就是一路next,中间如果你想选择环境的路径,可以选择,默认应该是C下的一个program的子目录。

  4. 环境变量配置(windows11为例):打开高级系统设置,打开环境变量,选择系统变量中的Path点击编辑,点击新建,输入在前面安装Go环境的路径配置。设置环境变量,👉 主要是希望通过配置环境变量后,OS会在执行Go程序的时候,会载入Go的配置进行解析。👓可以参考下列配置。在这里插入图片描述

  5. 校验:打开命令行工具,然后输入 go version,如果返回版本信息,说明你已经成功安装并配置了Go环境。在这里插入图片描述

2.1 ✨ IDE

  1. IDE 就是集成开发环境工具,说通俗点,就是我们编辑代码,构建运行以及调试的代码的工具。就是你可能之前用过的诸如vscode,vs studio, pycharm,idea,xcode等。

  2. 对于Go的IDE选择,主要有三个:

    1. vscode,严格来说,这个并不是IDE,而是一个轻量级的编辑器,但是可以通过安装一系列的拓展插件,比如Go插件实现的IDE级别的功能。好处在于轻量级,完全免费。
    2. GolandJetbrians针对Go开发的IDE,最新版的UI我很喜欢,当然用过idea 或者pychcarm 会发现界面非常像,因为是一个公司做的。黄同学使用的时候,感觉要比vscode 丝滑😄。缺点在于收费,而且小贵,基本上每年要100到200多​刀乐💵,相比pycharm,idea没有社区版,也就是说必须付费,但是有30天的体验期,且如果你和黄同学一样是个大学生(虽然很快就不是了),你可以申请免费使用了。
    3. IDEA Ultimate,必须是专业版哦,因为只有专业版才支持Go插件,使用上基本和Goland一致,但需要去配置Go插件,并且设置Go环境地址。好处在于你可以用这个IDE去玩很多语言,不只是Go和Java,还可以是Python等。

注意:如果你是简洁到极致的,你可以不用这些,至于运行,可以直接用 go run 或者 go build 命令。

2.2 Gitpod 和 Jetbrians Gateway 的使用

这个是黄同学在参加青训营的时候发现使用的,很好理解,就是青训营官方将代码上传到了Gitpod,其他人可以通过Gitpod 去编辑代码。

  1. Gitpod,其实就类似于像GitHub,是一个代码的托管方式,可以实现针对代码,进行远程的编辑。说白了,就是把代码放到服务器上,然后在本地端,通过网络实现对代码的编写,可以节省本地的内存资源。

  2. GitPod有多种连接端的选择,大致可以如下:

    1. 使用浏览器直接打开
    2. 使用vscode连接Gitpod
    3. 使用Jetbrians的ide,但是我在使用的时候发现,只能使用Jetbrians gateway来搭配使用。
  3. 个人体检,我并不是很习惯在直接使用Gitpod来操作Go代码程序,而是从这上面复制代码,然后放到本地端编写。

3. Go的基础语法

3.1 Hello World

万恶💀的开始,哈哈哈,开玩笑的。其实我们很多人的编程开始都是来自于那一句 Hello world

package main
​
import "fmt"func main() {
    fmt.Println("hello world")
}

通过这个作为开头,相信朋友们已经发现了,Go和Python类似,不需要打 ;来作为结尾。

3.2 变量与常量

  1. 变量有两种声明与初始化方式:

    1. 使用 var,变量名后面可用类型关键字(这一点好难受😭 ),当然也可以省略,如果要初始化用= 来初始化,会自动根据值确定类型,又Python一样。

          var a = "initial"
          var b, c int = 1, 2
          var d = true
          var e float64
      
    2. 使用 := 来声明并初始化变量,这个感觉更舒服一点。

          f := float32(e)
          g := a + "foo"
      
    3. 注意:go中并没有char,而是rune,是int32的别名,表示一个Unicode码点。

  2. 常量,你只需要在最开头用 const关键字,表示这是一个常量。类型关键字可写可不写。

        const s string = "constant"
        const h = 50000000
        const i = 3e20 / h
    

3.3 条件控制语句

  1. Go中有两种,一种是if-else,一种是 switch-case
  2. Go 不支持三目运算符,理由似乎是认为这个是不清晰的。

👉 if-else 👈

有几个细节:

  1. 不需要用括号包括条件
  2. 条件后需要直接跟花括号
  3. else 需要在if 的花括号后面使用
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }

👉 switch-case 👈

  1. 相比C的条件只支持int类型,go中的支持任意类型的变量。
  2. 同样,条件不需要括号
  3. 花括号和语句同行
  4. case 执行不需要break,默认会跳出
  5. case 可以多样化,甚至可以替代if-else。咳咳,这一点其实就是因为go对switch的实现就是用if-else来实现的。
    a := 2
    switch a {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    case 4, 5:
        fmt.Println("four or five")
    default:
        fmt.Println("other")
    }
​
    t := time.Now()
    fmt.Println("the time now is " + strconv.Itoa(t.Hour()) + "h") /* print the time's hour now */
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }

3.4 循环 👉 for 👈

  1. go代码中唯一的循环结构
  2. 不需要括号
  3. 最短可以不写循环条件,相当于只跑一次,当然也可以写C风格的for循环,也可以遍历某些容器。
    i := 1
    for {
        fmt.Println("loop")
        break
    }
    for j := 7; j < 9; j++ {
        fmt.Println(j)
    }
    for n := 0; n < 5; n++ {
        if n%2 == 0 {
            continue
        }
        fmt.Println(n)
    }
    for i <= 3 {
        fmt.Println(i)
        i++
    }

3.5 数组和切片

👉 array 👈

  1. 数组就和我们在C中一样,长度固定类型确定的元素序列。
  2. 支持 : 来快速选择子数组。
  3. 在初始化和声明的时候,必须在 [] 里边指定长度,如果不指定,那么就是切片。

        var a [5]int /* 所有元素初始化为0 */
        a[4] = 100
        fmt.Println("get:", a[2])
        fmt.Println("len:", len(a))
    ​
        b := [5]int{1, 2, 3, 4} /* 按序,未初始化部分则默认为0 */
        fmt.Println(b)
        fmt.Println("array's slice:", b[2:])
    ​
        var twoD [2][3]int
        twoD1 := [2][3]int{}
        fmt.Println("twoD1:", twoD1) /**/
        for i := 0; i < 2; i++ {
            for j := 0; j < 3; j++ {
                twoD[i][j] = i + j
            }
        }
        fmt.Println("2d: ", twoD)
        

👉 slice 👈

  1. 类似C++vector,是长度可变的数组。
  2. 有两种声明方式,一种是make,一种是和数组类似,但是不指定长度。
  3. 在使用append 的时候,需要赋值操作才能改变原数组的长度,其实是要接收长度变化后的结果。

        s := make([]string, 3)
        s[0] = "a"
        s[1] = "b"
        s[2] = "c"
        fmt.Println("get:", s[2])   // c
        fmt.Println("len:", len(s)) // 3
    ​
        s = append(s, "d")
        s = append(s, "e", "f")
        fmt.Println(s) // [a b c d e f]
    ​
        c := make([]string, len(s))
        copy(c, s)
        fmt.Println(c) // [a b c d e f]
    ​
        fmt.Println(s[2:5]) // [c d e]
        fmt.Println(s[:5])  // [a b c d e]
        fmt.Println(s[2:])  // [c d e f]
    ​
        good := []string{"g", "o", "o", "d"}
        fmt.Println(good) // [g o o d]

3.7 👉 map 👈

  1. 就和图,我们在算法经常使用的 哈希表 的概念实现。
  2. 简单的理解就是 key-value 的数据结构。

        m := make(map[string]int)
        m["one"] = 1
        m["two"] = 2
        fmt.Println(m)
        fmt.Println(len(m))
        fmt.Println(m["one"])
        fmt.Println(m["unknow"])
    ​
        r, ok := m["unknow"]
        fmt.Println(r, ok)
        r, ok = m["two"]
        fmt.Println(r, ok)
    ​
        delete(m, "one")
    ​
        m2 := map[string]int{"one": 1, "two": 2}
        var m3 = map[string]int{"one": 1, "two": 2}
        fmt.Println(m2, m3)

3.8 👉 range 👈

  1. 一般用于for循环结构
  2. 给容器创建迭代器,遍历容器中的元素

        nums := []int{2, 3, 4}
        sum := 0
        for i, num := range nums {
            sum += num
            if num == 2 {
                fmt.Println("index:", i, "num:", num)
            }
        }
        fmt.Println(sum)
    ​
        m := map[string]string{"a": "A", "b": "B"}
        for k, v := range m {
            fmt.Println(k, v)
        }
        for k := range m {
            fmt.Println("key", k)
        }

3.10 函数 参数

  1. 形参和c++ 类似,但是变量类型写后面
  2. 如果多个形参同类型,类型可以写在这些形参的最后一个

    func add(a int, b int) int {
        return a + b
    }
    ​
    func add2(a, b int) int {
        return a + b
    }
    
  1. 在参数后面,可以跟返回类型,甚至返回变量。

指针

  1. 支持指针,我觉得这个设计很棒,作为一个C++ 使用者。
  2. 语法基本等同C和C++。

    func add2(n int) {
        n += 2
        a := &n
        fmt.Println("add2:", *a)
    }

值传递 和 指针传递

  1. 值传递
        func add2(n int) {
            n += 2
            a := &n
            fmt.Println("add2:", *a)
        }
  1. 指针传递
        func add2ptr(n *int) {
            *n += 2
        }
  1. ⚠️ 注意:数组是值传递,切片是指针传递(传递指针,切片本身是包括了指向底层数组的指针,长度)

3.11 👉struct 👈结构体

  1. 没有class,只有struct
  2. 需要用type开头

    type user struct {
        name     string
        password string
    }

成员方法

  1. 在函数名前面用括号包括结构体变量。
  2. 结构体变量可以是指针,如果是指针,则可以修改成员变量。

    func (u user) checkPassword(password string) bool {
        return u.password == password
    }
    ​
    func (u *user) resetPassword(password string) {
        u.password = password
    }
    ​
    func main() {
        a := user{name: "wang", password: "1024"}
        a.resetPassword("2048")
        fmt.Println(a.checkPassword("2048")) // true
    }

3.12 👉 error 👈

  1. 可以在函数返回中包括正常返回的结果和错误两种类型。
  2. 这种机制要比java等那种try-catch结构要好一些,个人看法。

    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")
    }

3.13👉 string 👈字符串

  1. 需要导入 string 库。
  2. 接口功能很多,比如 ContainsCountJoinReplace等。

    func main() {
        a := "hello"
        fmt.Println(strings.Contains(a, "ll"))                // true
        fmt.Println(strings.Count(a, "l"))                    // 2
        fmt.Println(strings.HasPrefix(a, "he"))               // true
        fmt.Println(strings.HasSuffix(a, "llo"))              // true
        fmt.Println(strings.Index(a, "ll"))                   // 2
        fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
        fmt.Println(strings.Repeat(a, 2))                     // hellohello
        fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo, -1表示替换的数量没有限制
        fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
        fmt.Println(a)
        fmt.Println(strings.ToLower(a)) // hello
        fmt.Println(strings.ToUpper(a)) // HELLO
        fmt.Println(len(a))             // 5
        b := "你好"
        fmt.Println(len(b)) // 6 一个中文字相当于3个英文字符
    }

3.14 👉 fmt 👈

  1. 在前面 Hello World 的例子中,就用到了fmt,这个其实就是一个标准输出库(字符串格式化)。
  2. %v,任意类型的变量。
  3. %+v,详细结果。
  4. %#v,更详细。

        s := "hello"
        n := 123
        p := point{1, 2}
        fmt.Println(s, n) // hello 123
        fmt.Println(p)    // {1 2}
    ​
        fmt.Printf("s=%v\n", s)  // s=hello
        fmt.Printf("n=%v\n", n)  // n=123
        fmt.Printf("p=%v\n", p)  // p={1 2}
        fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
        fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

3.15 👉 json 👈

  1. 保证每一个(公开)字段的第一个字母是大写
  2. 序列化和反序列化的接口。

        a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
        buf, err := json.Marshal(a)
        if err != nil {
            panic(err)
        }
        fmt.Println(buf)
        fmt.Println(string(buf))
    ​
        var b userInfo
        err = json.Unmarshal(buf, &b)
        if err != nil {
            panic(err)
        }
        fmt.Printf("%v\n", b)

3.16 👉time👈

  1. 也需要导入包。
  2. 可以获取时间。
  3. 可以使用Date 接口设置时间,创建时间变量。
  4. 可以使用 Format 指定格式。

        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

3.17 👉 strconv 👈数字解析

  1. 主要是做数字解析,以及转换。比如说解析字符串数字。

        f, _ := strconv.ParseFloat("1.234", 64)
        fmt.Println(f) // 1.234
    ​
        n, _ := strconv.ParseInt("111", 10, 64)
        fmt.Println(n) // 111
    ​
        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

3.18 进程信息,获取系统参数

  1. 可以获取系统参数。
  2. 执行进程命令

        // go run example/20-env/main.go a b c d
        fmt.Println("Args:", os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
        fmt.Println("Path:", os.Getenv("PATH")) // /usr/local/go/bin...
        fmt.Println("Set:", 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

4. 个人经验

黄同学在这次编写以及实践中,总结了一些个人的一些经验。😙

  1. 可以多看官方文档,文档介绍的很详细。
  2. 多写代码,这是我觉得编程学习最好,最直接的方式。

参考资料

黄同学在编写这篇文章,除了自己的实践外,还参考了不少资料。如果朋友想要通过我的这篇简陋笔记文章去探索那些可以称为宝玉或者 💎 般的知识,不妨通过下面的链接看看:

  1. 《Go语言教程系列》介绍和环境安装 | Go主题月 - 掘金 (juejin.cn)
  2. 走进 Go 语言基础语法 - 掘金 (juejin.cn)
  3. Concatenate strings in Go (Golang) (gosamples.dev)
  4. [Declare a constant array in Golang SOLVED] | GoLinuxCloud](www.golinuxcloud.com/golang-arra…)