Go语言基础语法 | 青训营笔记

65 阅读6分钟

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

Go语言的特点

  1. 高性能、高并发
  2. 语法简单
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

变量声明

  • 可以按照以下方式声明变量:var intVal int 注意:

    var intVal int 
    intVal :=1   // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
    
    var intVal int 
    intVal :=1   // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
    
  • intVal := 1 相等于:

    var intVal int 
    intVal =1
    
  • 多变量声明

    //类型相同多个变量, 非全局变量
    var vname1, vname2, vname3 type
    
    vname1, vname2, vname3 = v1, v2, v3
    
    var vname1, vname2, vname3 = v1, v2, v3    // 和 python 很像,不需要显示声明类型,自动推断
    
    vname1, vname2, vname3 := v1, v2, v3      
    // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
    
    // 这种因式分解关键字的写法一般用于声明全局变量
    var (
        vname1 v_type1
        vname2 v_type2
    )
    
  • 空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

go 控制流

  • 判断语句 if-else
  • 循环语句 for
  • 条件分支语句 switch-case

go基本数据类型、函数、字符串操作

  • 数组

  • slice 切片 append结果需要赋值回原来的切片

    s = append(s, "d")
    

    不同于 python,go 切片不支持负数索引

  • map,查看 map 中是否有某元素时,用两个值接收

    m := make(map[string]int)
    m["one"] = 1
    m["two"] = 2
    
    r, ok := m["unknow"]
    fmt.Println(r, ok) // 0 false
    
  • range 快速遍历 slice 与 map slice 返回两个值: index,value map 返回 key,value

    func main() {
        nums := []int{2, 3, 4}
        sum := 0
        for i, num := range nums {
            sum += num
            if num == 2 {
                fmt.Println("index:", i, "num:", num) // index: 0 num: 2
            }
        }
        fmt.Println(sum) // 9
    
        m := map[string]string{"a": "A", "b": "B"}
        for k, v := range m {
            fmt.Println(k, v) // b 8; a A
        }
        for k := range m {
            fmt.Println("key", k) // key a; key b
        }
    }
    
  • 函数参数相同类型可以只在最后一个参数后面指定类型 函数可以返回多个返回值,在业务中,第一个值返回需要返回的值,第二个值返回错误信息

    import "fmt"
    
    func add(a int, b int) int {
        return a + b
    }
    
    func add2(a, 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
    }
    
    func main() {
        res := add2(1, 2)
        fmt.Println(res) // 3
    
        v, ok := exists(map[string]string{"a": "A"}, "a")
        fmt.Println(v, ok) // A True
    }
    
  • go 指针 类似c/c++ 中的指针,可以用于在函数内部修改变量的值

    import "fmt"
    
    func add2(n int) {
        n += 2
    }
    
    func add2ptr(n *int) {
        *n += 2
    }
    
    func main() {
        n := 5
        add2(n)
        fmt.Println(n) // 5
        add2ptr(&n)
        fmt.Println(n) // 7
    }
    
  • 结构体:带类型的字段的集合 定义以及使用一个结构体:

    type struct_name struct {
        var1 var1_type
        var2 var2_type
    }
    
    // 例子
    import "fmt"
    
    type user struct {
        name     string
        password string
    }
    
    func main() {
        a := user{name: "wang", password: "1024"}
        b := user{"wang", "1024"}
        c := user{name: "wang"}
        c.password = "1024"
        var d user
        d.name = "wang"
        d.password = "1024"
    
        fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
        fmt.Println(checkPassword(a, "haha"))   // false
        fmt.Println(checkPassword2(&a, "haha")) // false
    }
    
    func checkPassword(u user, password string) bool {
        return u.password == password
    }
    
    func checkPassword2(u *user, password string) bool {
        return u.password == password
    }
    

    作为函数参数,可以修改结构体,避免某些大结构体拷贝的开销

  • 结构体方法: 注意这里的方法与前面的方法的不同之处。 注意:编译器会隐式的获取变量的地址

    import "fmt"
    
    type user struct {
        name     string
        password string
    }
    
    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
    }
    
  • 错误处理:在函数参数中添加 error 类型的参数,表示函数具有错误处理;需要返回值时,返回两个值,第一个是要返回的值,第二个是错误信息

    // user 为前面定义的结构体
    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")
    }
    
    // 调用函数
    
    if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
            fmt.Println(err) // not found
            return
        } else {
            fmt.Println(u.name)
        }
    
  • go 字符串操作 go 标准库 strings 用例: 注意 replace 用法 func Replace(s, old, new string, n int) string 返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。

    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
      // 子串在字符串中的开始 index
      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
      fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
      fmt.Println(strings.ToLower(a))                       // hello
      fmt.Println(strings.ToUpper(a))                       // HELLO
      // 获取字符串长度
      fmt.Println(len(a))                                   // 5
      b := "你好"
      fmt.Println(len(b)) // 6
    }
    
  • 字符串格式化 fmt.Printf 直接使用 %v 打印变量的值,%+v 会打印结构体字段的名称,%#v 会打印整个结构体的类型名称以及字段名称和值

    // 结构体定义
    type point struct {
        x, y int
    }
    
    p := point{1, 2}
    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}
    
  • json操作 在库 "encoding/json" 序列化: 结构体的字段第一个字母大写时,使用 json.Marshal 将其序列化,返回两个参数,第一个为序列化结果,第二个为错误信息。打印序列化结果时,将序列化结构转化为 string 可以打印出 json 格式的文本。 可以在字段末尾加上一个 json 的 tag 表示序列化后的键。 格式化输出:json.MarshalIndent() 方法 反序列化: 使用json.Unmarshal 方法进行反序列化,第一个参数为需要反序列化的变量,第二个参数为接收地址,返回错误信息 使用例:

    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"}}
    }
    
  • 时间处理 time 包 注意格式化 t.Format("2006-01-02 15:04:05") layout直接就是 2006-01-02 15:04:05 将字符串转化为日期: t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")

  • 字符串与数字的转换 在 strconv 包之中 strconv.ParseInt(s string, base int, bitSize int) base 是进制,0自动推断;bitSize是位数

  • 字符串与十进制数的快速转换

        n2, _ := strconv.Atoi("123")
        fmt.Println(n2) // 123
        n2, err := strconv.Atoi("AAA")
        fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
    

实战

  • 猜谜游戏:如何生成随机数,读取用户输入以及输出结果,函数控制流的使用
  • 在线词典:抓包,使用代码生成工具
  • socks5 协议:编写 socks5 代理服务器,contex 的使用,goroutine 的使用

总结

在熟悉其他语言的前提下,学习go语言的基础知识还是比较简单的。