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

166 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。 今天听了第一次课,学到了超多东西,我的基础比较薄弱,就在笔记里记录了很多课堂知识点,打算日后温故而知新。

本堂课重点内容:

Go语言基础语法

三个项目的实战(猜谜游戏、在线词典、Socks5 代理)

什么是go语言

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

哪些公司在使用Go语言

字节、Google、腾讯、facebook、美团、七牛云、滴滴······

字节为什么会全面拥抱Go语言

  1. 一开始是Python,性能问题换成Go

  2. C++不太适合在线Web业务

  3. 早期团队非Java背景

  4. 性能比较好

  5. 部署简单、学习成本低

  6. 内部RPC和HTTP框架的推广

入门

开发环境 - 安装golang

配置集成开发环境(VSCode+go插件、Golang)

我用的是VSCode+插件,是跟着B站视频配置成功的:【Go】三分钟搭建VSCode的Go语言开发环境-2020.9版

基于云的开发环境(github)

基础语法

1 Hello World

文件属于main包的一部分,是程序的入口文件
导入标准库中的fmt包,fmt包主要作用:往屏幕输入输出字符串、格式化字符串

 package main//文件属于main包的一部分,是程序的入口文件
 ​
 import (
     "fmt"//导入标准库中的fmt包,fmt包主要作用:往屏幕输入输出字符串、格式化字符串
 )
 ​
 func main() {
     fmt.Println("hello world")//输出
 }

两种方法来运行本文件 image-20220507103335106

2 变量

 func main() {
 ​
     var a = "initial"//声明变量(自动匹配类型)
 ​
     var b, c int = 1, 2
 ​
     var d = true
 ​
     var e float64
 ​
     f := float32(e)//声明变量
 ​
     g := a + "foo"
     fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
     fmt.Println(g)                // initialapple
 ​
     const s string = "constant"//常量(根据上下文自动确定类型)
     const h = 500000000
     const i = 3e20 / h
     fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
 }

3 if else

  //if后面的条件是没有括号的      //语句一定要加括号

 func main() {
     //if后面的条件是没有括号的
     //语句一定要加括号
     if 7%2 == 0 {
         fmt.Println("7 is even")
     } else {
         fmt.Println("7 is odd")
     }
 }

4 循环

Go里面只有for循环
//for后面什么都不写代表死循环 格式跟if else的很像条件是没有括号的,语句一定要加括号

5 switch

括号习惯跟前两个一样
case里不用加break也能自动跳出

6 数组

具有编号,且长度固定的元素序列

 func main() {
 ​
     var a [5]int//数组a
     a[4] = 100
     fmt.Println("get:", a[2])
     fmt.Println("len:", len(a))
 }

7 切片slice

切片 = 长度+容量+指向数组的指针
append追加元素,要把它赋值回原数组(会扩容,要更新数组)
copy用于在两个切片里拷贝数据
还有类似于Python的切片操作

8 map

类似于其他语言里的哈希/字典
要注意输出时的顺序是随机的

 package main
 ​
 import "fmt"
 ​
 func main() {
     m := make(map[string]int)//(map[key的类型]val的类型)
     m["one"] = 1//写入
     m["two"] = 2
     fmt.Println(m)           // map[one:1 two:2]
     fmt.Println(len(m))      // 2
     fmt.Println(m["one"])    // 1
     fmt.Println(m["unknow"]) // 0
 ​
     r, ok := m["unknow"]//ok——有没有key存在
     fmt.Println(r, ok) // 0 false
 ​
     delete(m, "one")//删除
 ​
     m2 := map[string]int{"one": 1, "two": 2}
     var m3 = map[string]int{"one": 1, "two": 2}
     fmt.Println(m2, m3)//输出顺序随机
 }

9 range

快速遍历slice、map
类似于for(auto& k,v : map)

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

10 函数

函数的变量类型是后置的
有的能返回两个返回值,返回 结果+错误信息(是否存在)

11 指针

指针的重要作用在于对传入的参数进行修改

12 结构体

带类型的字段的集合

13 结构体方法

类成员函数

 package main
 ​
 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
 }

14 字符串操作

//字符串里是否包含另一个字符串   fmt.Println(strings.Contains(a, "ll"))
//字符串计数   fmt.Println(strings.Count(a, "l"))
//查找某个字符串的位置   fmt.Println(strings.Index(a, "ll"))
//连接多个字符串   fmt.Println(strings.Join([]string{"he", "llo"}, "-"))
//重复多个字符串   fmt.Println(strings.Repeat(a, 2))
//一个中文会对应多个字符   b := "你好"   fmt.Println(len(b)) // 6

15 字符串格式化

类似于C语言的printf,但类型都能用%v搞定
%+v打印出详细的值
%#v打印出更加详细的值

16 时间处理

用特定时间格式化一个时间到时间字符串                 // 2022-03-27 01:25:36

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 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//获取时间戳
 }

17 数字解析

strconv.ParseInt("111", 10, 64)//(字符串,进制,64位精度的整数)
Atoi:十进制->数字 Itoa:数字->字符串

 package main
 ​
 import (
     "fmt"
     "strconv"//string convert
 )
 ​
 func main() {
     f, _ := strconv.ParseFloat("1.234", 64)
     fmt.Println(f) // 1.234
 ​
     n, _ := strconv.ParseInt("111", 10, 64)//(字符串,进制,64位精度的整数)
     fmt.Println(n) // 111
 ​
     n, _ = strconv.ParseInt("0x1000", 0, 64)
     fmt.Println(n) // 4096
 ​
     n2, _ := strconv.Atoi("123")//Atoi:十进制->数字  Itoa:数字->字符串
     fmt.Println(n2) // 123
 ​
     n2, err := strconv.Atoi("AAA")
     fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
 }

18 进程信息

fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...//获取环境变量   fmt.Println(os.Setenv("AA", "BB"))//写入环境变量

实战

1 猜谜游戏

完整游戏代码

修改的内容都在注释里

 package main
 ​
 import (
     "bufio"
     "fmt"
     "math/rand"
     "os"
     "strconv"
     "strings"
     "time"
 )
 ​
 func main() {
     //生成随机数
     maxNum := 100
     rand.Seed(time.Now().UnixNano())//设置随机数种子(用时间戳)
     secretNumber := rand.Intn(maxNum)
     // fmt.Println("The secret number is ", secretNumber)
 ​
     //读取用户的输入输出
     fmt.Println("Please input your guess")
     reader := bufio.NewReader(os.Stdin)
     //实现游戏循环
     for {
         input, err := reader.ReadString('\n')
         if err != nil {
             fmt.Println("An error occured while reading input. Please try again", err)
             continue
         }
         input = strings.TrimSuffix(input, "\r\n")//win系统要这样写
 ​
         guess, err := strconv.Atoi(input)
         if err != nil {
             fmt.Println("Invalid input. Please enter an integer value")
             continue
         }
         fmt.Println("You guess is", guess)
         
         //实现判断逻辑
         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 在线词典

输入单词,输出音标、解释

抓包

fanyi.caiyunapp.com/

进去之后翻译good,然后右键点检测

image-20220507135145508

image-20220507134941227

代码生成

我们需要生成代码,这里有个巧妙的办法

右键找到的dict->COPY->COPY as cURL(bash) 复制粘贴到网站:curlconverter.com/#go

image-20220507141029496

生成下面的go语言代码

 package main
 ​
 import (
     "fmt"
     "io/ioutil"
     "log"
     "net/http"
     "strings"
 )
 ​
 func main() {
     //创建http.Client
     client := &http.Client{}
     var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)//字符串转化成流
     //创建请求
     req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)//(method,url,data)参数是个流(占内存小)
     if err != nil {
         log.Fatal(err)
     }
     //设置请求头
     req.Header.Set("Accept", "application/json, text/plain, */*")
     req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
     req.Header.Set("Connection", "keep-alive")
     req.Header.Set("Content-Type", "application/json;charset=UTF-8")
     req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
     req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
     req.Header.Set("Sec-Fetch-Dest", "empty")
     req.Header.Set("Sec-Fetch-Mode", "cors")
     req.Header.Set("Sec-Fetch-Site", "cross-site")
     req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36")
     req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
     req.Header.Set("app-name", "xy")
     req.Header.Set("os-type", "web")
     req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"`)
     req.Header.Set("sec-ch-ua-mobile", "?0")
     req.Header.Set("sec-ch-ua-platform", `"Windows"`)
     //发起请求
     resp, err := client.Do(req)
     if err != nil {
         log.Fatal(err)
     }
     defer resp.Body.Close()//防止资源泄漏,defer手动关闭流,defer在函数结束后从下往上触发
     //读取响应
     bodyText, err := ioutil.ReadAll(resp.Body)
     if err != nil {
         log.Fatal(err)
     }
     fmt.Printf("%s\n", bodyText)
 }

上面代码运行之后会产生下面的东西

image-20220507142844235

生成 request body

构造一个json构造体,字段与json一致,实现json序列化

解析 response body

一开始翻译网页的Preview

image-20220507144134105

右键copy object复制到 oktools.net/json2go 转换-嵌套成go,并且将改名成 DictResponse

image-20220507144433136

修改代码

将此时的DictResponse结构体添加到上面的代码中,并且将最后一行输出(90行)删掉,加上91-96行代码

image-20220507151909498

 //打印结构体
 var dictResponse DictResponse
 err = json.Unmarshal(bodyText, &dictResponse)
 if err != nil {
     log.Fatal(err)
 }
 fmt.Printf("%#v\n", dictResponse)

输出结果

image-20220507150409130

打印结果

image-20220507150735836

 //出错处理
 if resp.StatusCode != 200 {
         log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
     }
 //留下音标、解释的信息
 fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
     for _, item := range dictResponse.Dictionary.Explanations {
         fmt.Println(item)
     }

完善代码

  1. import添加 “os”
  2. 将main()函数修改名称为query(word string)函数,并且Source原先的"good"更改成变量word
  3. 重新写个main()函数
 func main() {
     if len(os.Args) != 2 {
         fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
 example: simpleDict hello
         `)
         os.Exit(1)
     }
     word := os.Args[1]
     query(word)
 }

执行结果:

image-20220507151305990

3 Socks5 代理

TCP echo server(发送啥、回复啥)

 package main
 ​
 import (
     "bufio"
     "log"
     "net"
 )
 ​
 func main() {
     server, err := net.Listen("tcp", "127.0.0.1:1080")//增添端口
     if err != nil {
         panic(err)
     }
     for {
         client, err := server.Accept()
         if err != nil {
             log.Printf("Accept failed %v", err)
             continue
         }
         go process(client)//go类似于启动子线程
     }
 }
 ​
 func process(conn net.Conn) {
     defer conn.Close()//退出时关掉
     reader := bufio.NewReader(conn)//只读流(带缓冲)
     for {
         b, err := reader.ReadByte()//每次读一个字节
         if err != nil {
             break
         }
         _, err = conn.Write([]byte{b})//类型转换写入字节
         if err != nil {
             break
         }
     }
 }

先运行程序,再输入指令nc 127.0.0.1 1080如果你出现了以下报错

image-20220507185336177

那是因为win不自带netcat,要自己下载,感谢有同学分享的链接,让我成功运行此程序

Windows 下载安装 netcat(nc)命令

之后你输入,它就会输出同样的内容

image-20220507185731050

auth

请求阶段

relay阶段

后面的部分我还没搞定,思路是这样的,但具体实现我还没弄好

课后总结

青训营的内容太丰富了!我记了好久好久的笔记,那三个实战我还没完全理解,之后一定要找时间自己试着实现一遍