GO语言基础与配套实例 | 青训营笔记

77 阅读2分钟

GO语言基础与配套实例 | 青训营笔记


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

1、GO环境配置

  • Go官网下载对应安装包

  • (Windows)配置好安装目录下bin的环境变量

image-20230114154944490.png

  • 配置依赖包的代理,cmd下运行

    $env:GO111MODULE = "on"
    $env:GOPROXY = "https://goproxy.cn"
    
  • 配置开发环境 安装VS Code或者GoLand,其中使用VS Code需要自己安装Go插件 image-20230114155554513.png


2、配套学习实例

使用Git (git-scm.com)克隆课程示例项目wangkechun/go-by-example (github.com)

克隆后项目目录

image-20230114160317700.png


3、GO基础语法

  • 变量定义
// 变量定义
var name1 string            //var + 变量名 + 类型名称
var name2, name3 int = 1, 2 //连续定义
var name4 = "hello"         //自动类型推断
name5 := "world"            //变量定义方式二const width = 100           //常量定义使用关键字const
  • for循环
// go中循环只有for一种关键字
// 常规for循环
for int i = 0; i < 10; i++ {
    do()                    
}
​
// 其他语言的while循环在go中的实现
for i <= 10 {
    do()
}
​
// 类似while(true)效果
for {
    do()
    break
}
  • if判断
// 常规判断
if i < 10 {
    do()
}
​
// 在if中定义局部变量,只可在if方法块中访问
if i := 10; i < 20 {
    do()
}else {
    do()
}
  • switch语句

    • go中的switch语句会自动在执行完case后,隐式break退出,不像其他语言会继续执行接下来的case块。
    • 想要继续执行接下来的语句块,使用fallthrough关键字可以执行下一条case。 但是不检查下一条case的条件
    switch f := 5; {
        // switch中的条件可以为空,即为switch true{}
        case f < 3: 
            fmt.Println("3")
        // 正常情况下,执行完这一条case就直接退出switch
        case f < 6:
            fmt.Println("6")
            // 但是由于fallthrough关键字的存在,会执行下一条case中的语句,但不会检查case的条件。
            fallthrough
        case f < 4:
            fmt.Println("4")
    }
    
  • 数组

    // 数组声明
    var a [5]int
    // 数组赋值
    a[3] = 19
    // 数组访问
    fmt.Println(a[3])
    // 数组声明时定义
    b := [3]int{1, 2, 3}
    // 二维数组
    var two [2][1]int
    
  • 切片 切片的本质是对数组片段的描述,其包含三个主要的内容:指向数组的指针、片段的长度和容量

image-20230115103837411.png

```
// 创建切片
s := make([]string, 3)  // make()中第一个参数为创建的类型,可以是slice, map和channel三者。第二个参数为数据类型所占有内存空间长度,当type是slice时,此项为必选项。第三个参数为预留内存空间长度,为可选参数
s[0] = "a"
s[1] = "b"
s[2] = "c"// 向切片中添加元素,使用append()函数
s = append(s, "d")
s = append(s, "e", "f")
​
// 切片中没有之间的删除元素操作,必须手动现实
letters := []string{"A", "B", "C", "D", "E"}
remove := 2
letters = append(letters[:remove], letters[remove + 1:]...)
​
// 使用copy()函数可以创建切片的副本
slices2 = make([]string, 3)
copy(slices, letters[1:4])
```
  • 映射

    • 添加:直接使用键值对即可studentAge["Tom"] = 11 注意:添加操作是对于已经初始化的映射变量而言,如果对nil映射进行添加操作将报错。

      var student map[string]int
      student["Tom"] = 1
      // 这将报错,因为此时student为nil
      
    • 访问:直接像访问数组一样访问student["Tom"] 当访问不存在的kv对时,Go不会panic,而是返回默认值。在 Go 中,映射的下标表示法可生成两个值。 第一个是项的值。 第二个是指示键是否存在的布尔型标志

      age, exist := studentAge["Tom"],可以通过exist来判断返回的value是默认值还是确切的项。

    • 删除: 如果要从映射中删除项,需要使用内置函数delete()。 delete(map, key):如delete(studentAge, "Tom"),即可删除"Tom"的映射。 当删除不存在的映射时,Go不会panic

    // 创建映射
    m := make(map[string]int)   // 其中map[]中为key的类型,[]后为value的类型
    m["one"] = 1
    m["two"] = 2// 访问映射
    fmt.Println(m["one"])       // 输出:1
    
  • range

    • 当对数组使用range时,返回两个参数:下标和对应值
    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
        }
    }
    
    • 当对map使用range时,返回key和value值
    m := map[string]string{"a": "A", "b": "B"}
    for k, v := range m {
        fmt.Println(k, v) // b 8; a A
    }
    
  • func函数

    go中函数可以有多个返回值:func methodName(args ...argsType) (returnValue ...valueType)

    • 无返回值的func:func method1(a int, b int){...}

    • 一个返回值的func:func method2(a int, b int) int {...}

    • 多个返回值的func:func method3(a int, b int) (int, int){...}

    • 返回值可以指定名称:

      func add(a int, b int) int {
          return a + b;
      }
      ​
      // 当指定返回值名称后
      func add(a int, b int) (res int){
          res = a + b
          return
      }
      
  • 指针

    go中的指针类型和使用和c中很类似,但是go中指针没有值运算,其主要作用就在于修改值。因为go中的参数传递是值传递,如果函数间传递的参数为大数组之类的很占空间的变量,值传递会带来很严重的性能下降。而指针可以很好的解决这个问题。

    var p *int 
    fmt.Printf("%v\n",p) //← 打印 nilvar i int //← 定义一个整形变量 i
    p = &i    //← 使得 p 指向 i, 获取 i 的地址
    fmt.Printf("%v\n",p) //打印内存地址
    ​
    *p = 6
    fmt.Printf("%v\n",*p) //打印6
    
  • 结构体

    • 结构定义

      type Employee struct {
          ID        int
          FirstName string
          LastName  string
          Address   string
      }
      
    • 结构声明

      var john Employee
      
    • 带初始化的声明

      employee := Employee{1001, "John", "Doe", "Doe's Street"}
      
    • 初始化的顺序不重要

      employee := Employee{LastName: "Doe", FirstName: "John"}
      
    • 访问结构中的属性

      employee.ID = 1001
      fmt.Println(employee.FirstName)
      
    • 结构指针

      package main
      ​
      import "fmt"type Employee struct {
          ID        int
          FirstName string
          LastName  string
          Address   string
      }
      ​
      func main() {
          employee := Employee{LastName: "Doe", FirstName: "John"}
          fmt.Println(employee)
          employeeCopy := &employee
          employeeCopy.FirstName = "David"
          fmt.Println(employee)
      }
      

      注意上面指针变量employeeCopy在获取employee实例的属性时,不用使用*employeeCopy属性,而是直接使用.来获取属性内容。

    • 结构嵌入

      type Person struct {
          ID        int
          FirstName string
          LastName  string
          Address   string
      }
      

      可以在另一个结构中直接嵌入Person

      type Employee struct {
          Information Person
          ManagerID   int
      }
      

      但是此时要使用Person中的属性,就得包含Information字段:employee.Information.FirstName = "John"

      或者可以嵌入一个和结构同名的字段

      type Employee struct {
          Person
          ManagerID int
      }
      
      package main
      ​
      import "fmt"type Person struct {
          ID        int
          FirstName string
          LastName  string
          Address   string
      }
      ​
      type Employee struct {
          Person
          ManagerID int
      }
      ​
      type Contractor struct {
          Person
          CompanyID int
      }
      ​
      func main() {
          employee := Employee{
              Person: Person{
                  FirstName: "John",
              },
          }
          employee.LastName = "Doe"
          fmt.Println(employee.FirstName)
      }
      
  • 结构方法(类似oop中的对象方法) 在普通方法的定义基础上,加上前缀(structVarName structType),就可以指定为某个结构体的方法

    type user struct {
        name     string
        password string
    }
    ​
    func (u user) checkPassword(password string) bool {
        return u.password == password
    }
    
  • error 在go中,使用error传递错误,可以类比java中的异常。可以使用errors.New()新建error

    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")
    }
    
  • string string是go中基础类型的一种。配合strings包可以实现许多字符串操作

    strings.Contains(s string, sub string)  // 判断是否包含字符串
    strings.Count(s string, sub string)     // 统计子字符串出现的次数
    strings.HasPrefix(a, "he")               // 判断是否以sub为前缀
    strings.HasSuffix(a, "llo")              // 判断是否以sub为后缀
    strings.Index(a, "ll")                   // 定位子字符串的起始位置
    strings.Join([]string{"he", "llo"}, "-") // 字符串拼接
    strings.Repeat(a, 2)                     // 重复字符串
    strings.Replace(a, "e", "E", -1)         // 字符串替换
    strings.Split("a-b-c", "-")              // 字符串分割
    strings.ToLower(a)                       // 全小写
    fmtstrings.ToUpper(a)                   // 全大写
    
  • fmt包 格式化包format的缩写,最常用的是其中的字符串输出函数

    fmt.Printf()
    fmt.Println()
    fmt.Sprintf()
    fmt.Sprintln()
    
  • json包

    json.Marshal(a)     // 对结构体对象进行json序列化操作,返回btye数组
    ​
    json.Unmarshal(buf, &b)     // 对json格式进行反序列化操作,存储在b中
    
  • time包

    u := time.Now()         // 返回当前时间
    time.Date(year int, month Month, day int, hour int, min int, sec int, nsec int, loc *Location)  //返回由参数实例化的Time对象
    time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")    // 将第二个参数根据第一个参数实例化Time对象
    ​
    u.Unix()                // Time对象调用Unix()方法可以返回时间戳
    
  • strconv包 stringconvert的缩写,字符串转换包。

    strconv.ParseFloat("3.14", 64)      // 字符串转float类型,第二个参数为转换后位数
    strconv.ParseInt("111", 10, 64)     // 字符串转int类型,第二个参数为数字字符串的基数,第三个参数为转换后的位数
    strconv.Atoi("123")                 // 字符串转int类型
    strconv.Itoa(123)                   // int类型转字符串
    
  • os包

    os.Args                             // 返回main调用时的传入参数
    os.Getenv("PATH")                   // 获取环境变量
    os.Setenv("AA", "BB")               // 设置环境变量
    os.exec.Command("bash", "-c", cmdStr)   // 执行commond命令
    

4、3个实例练习

  1. 猜谜游戏

    1. 最终版本代码猜谜游戏

    2. 效果展示

      image-20230115133059279.png

    3. rand.Seed(time.Now().UnixNano())使用时间戳作为随机种子,保证每次输出都是随机

    4. reader := bufio.NewReader(os.Stdin)使用os包读入输入内容至缓冲区

    5. input, err := reader.ReadString('\n')从缓冲区内读入一行(\n为换行符,接收到\n结尾即为一行)

    6. input = strings.Trim(input, "\r\n")将读入的一行移除指定字符串(\n\r)的前后缀

    7. guess, err := strconv.Atoi(input)将读入的字符串转换为int类型变量

    8. 将用户输入的值与随机生成的数字进行比较,相同则结束,否则继续循环

      if guess > secretNumber {
          fmt.Println("Your guess is bigger than the secret number. Please try again")
      } else if guess < secretNumber {
          fmt.Println("Your guess is smaller than the secret number. Please try again")
      } else {
          fmt.Println("Correct, you Legend!")
          break
      }
      
  2. 命令行字典

    1. 最终版本代码命令行字典

    2. 效果展示

      image-20230115133230936.png

    3. 首先定义好请求包DictRequest和相应包DictResponse的格式(使用json定义)

    4. client := &http.Client{}定义http客户端对象

    5. 绑定请求包方式和请求api接口

      var data = bytes.NewReader(buf)
      req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
      
    6. resp, err := client.Do(req)发送请求,接受响应

    7. defer resp.Body.Close()使用go中的延迟执行语句,延迟关闭流

    8. 接收响应体文本,json反序列化内容为对象

      bodyText, err := ioutil.ReadAll(resp.Body)
      err = json.Unmarshal(bodyText, &dictResponse)
      
  3. SOCKS5代理

    1. 最终版本代码socks5代理

    2. 效果展示

      image-20230115145315088.png

    3. server, err := net.Listen("tcp", "127.0.0.1:1080")监听1080端口

    4. go process(client)使用协程处理连接

    5. defer conn.Close()延迟处理关闭连接流

    6. 启动socks5中的认证和连接函数

      err := auth(reader, conn)
      err = connect(reader, conn)
      
    7. connect()函数中最重要的是实现代理服务器和访问客户端的双向通信

      ctx, cancel := context.WithCancel(context.Background())
      defer cancel()
      ​
      go func() {
          _, _ = io.Copy(dest, reader)
          cancel()
      }()
      go func() {
          _, _ = io.Copy(conn, dest)
          cancel()
      }()
      ​
      <-ctx.Done()                // 为了避免协程的启动即结束,使用channel来等待context的执行完毕