后端day1-基础语法与实战案例 | 青训营笔记

101 阅读7分钟

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

一、本堂课重点内容

本节课程主要分为三个方面:

  1. Go 语言简介
  2. Go 语言开发入门,包括开发环境配置、基础语法、标准库
  3. Go 实战,包括三个实战项目

二、详细知识点介绍

  1. Go语言开发环境搭建(已完成)

  2. 第一行代码:Hello World

    package main  // 程序为main包的一部分
    import "fmt"  // 导入标准包
    func main(){
        fmt.Println("Hello World!")	
    }
    

    使用go run直接运行,go build编译为二进制。

  3. 变量与常量

    声明变量的几种方式:

    //第一种:指定变量的类型,并且赋值
    var num int = 18
    //第二种:指定变量的类型,但是不赋值,使用默认值 
    var num2 int
    //第三种:如果没有写变量的类型,那么根据=后面的值进行判定变量的类型
    var num3 = "tom"
    //第四种:省略var,用 := 自动推导类型
    sex := "男"
    //声明多个变量:
    var n1,name,n2 = 10,"jack",7.8
    n6,height := 6.9,100.6
    

    声明常量的几种方式:

    const s string = "constant"
    const h = 12345
    
  4. 条件语句与循环语句

    if的写法与c或c++类似,但if后没有括号,且格式必须为大括号不换行:

    if ... {
    	...
    } else {
    	...
    }
    

    switch中不需要写break,不会跑下面的case分支:

    a := 2
    switch a {
    	case 1:
    		...
    	case 2:
    		...
    	default:
    		...
    }
    

    循环语句只有for,用法与c或c++类似,但没有括号:

    // 死循环
    for{
    	...
    }
    // 类似c++中的for
    for i := 1; i < 10; i++ {
    	...
    }
    // 类似c++中的while
    for i <= 3{
    	...
    	i = i + 1
    }
    

    同样的,循环语句中可以使用break或continue。

  5. 数组与切片

    数组的声明方式:

    var a [5]int //空数组
    b := [5]int{1,2,3,4,5} //给数组赋初值
    

    切片是可变长度的数组:

    //创建一个切片,并确定数据类型和默认长度
    s := make([]string, 3)
    //追加元素
    s = append(s, "d")
    s = append(s, "e", "f")
    //复制一个切片
    c := make([]string, len(s)) //取切片长度:len(s)
    copy(c,s)
    //可以用类似于python的操作取值
    fmt.Println(s[1:3])
    
  6. map的使用

    注:go中的map是无序的。

    //创建一个map,并确定数据类型
    m := make(map[string]int)
    //创建一个map并初始化
    m2 := map[string]int{"one": 1,"two": 2}
    //写入键值对
    m[“one”] = 1
    //读取键值对,ok表示map中是否存在这个键
    r, ok := m["unknow"] // 0 false
    //删除键值对
    delete(m,"one")
    
  7. range的使用

    // 对于数组返回两个值:索引和对应位置的值
    nums:= []int{2,3,4}
    for _, num := range nums{ //若不需要索引可以用_来表示
    	...
    }
    // 对于map返回两个值:键和值
    m := map[string]int{"one": 1,"two": 2}
    for k, v := range m{
    	...
    }
    
  8. 函数

    go中返回值是后置的,允许有多个返回值。

    带有指针的函数与c语言类似,使用&和*。

    func add(m map[string]int, b string) (c int, ok bool){
    	c, ok = m[b]
    	return c, ok
    }
    
  9. 结构体

    //结构体定义
    type user struct{
    	name string
    	password string
    }
    //结构体方法,类似于类成员函数,与普通函数的区别是函数名之前有类成员
    func (u user) checkPassword(password string) bool {
    	return u.password == password
    }
    func main(){
    	// 结构体的初始化
    	a := user{name: "wang", password:"1234"}
    	b := user{"li","5678"}
    	// 用.来访问元素
    	b.name = "zhou"
    	ok := a.checkPassword("1234") // true
    }
    
  10. 错误处理

    func findUser(users []user, name string) (v *user, err error){
    	for _, u : range users{
    		if u.name == name{
    			return &u, nil //若没有错误,返回nil
    		}
    	}
        //用errors.New返回错误
    	return nil, errors.New("now found")
    }
    
  11. 字符串操作

    go中的“strings”包中含有许多字符串操作函数,import后即可使用。

    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
    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
    
  12. 格式化输出

    使用fmt.Printf()可以实现类似于c语言的格式化输出,但可以直接使用%v来输出,不需要判断数据类型。

    使用%+v, %#v等,可以得到更加详细的输出。

  13. JSON处理

    对于一个结构体,只需要保证每个字段的第一个字母是大写(即公开字段),则可以使用json.Marshal()来序列化。

    json.Marshal()返回两个参数:一个是序列化后的值,一个是error。

    可以使用json.Unmarshal()来进行反序列化,得到原来的结构体。

    type user struct{
    	name string
    	password string
    }
    func main(){
        //序列化
        a := user{name: "wang", password:"1234"}
        buf, err := json.Marshal(a)
        if err != nil{
            panic(err)
        }
        fmt.Println(string(buf))
        // 反序列化
        var b user
        err = json.Unmarshal(buf, &b)
        if err != nil{
            panic(err)
        }
    }
    
  14. 时间处理

    常用的时间处理函数在“time”包中。

    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
    
  15. 数字解析

    常用的函数在“strconv”包中。

    //将字符串解析成数字
    f, _ := strconv.ParseFloat("1,234", 64) //第二个参数表示精度
    n, _ := strconv.ParseInt("1234", 10, 64) //第二个参数表示进制
    n2, _ := strconv.Atoi("123")
    n3, err := strconv.Atoi("abc") //如果字符串不是一个数字,报错
    
  16. 进程信息

    需要import "os" 以及 import "os/exec"

    // go run main.go a b c d
    os.Args //参数,第一个成员为自身的路径和名字,后面几个成员为a b c d
    os.Getenv("PATH") //获取环境变量
    os.Setenv("AA","BB") //写入环境变量
    
    // 执行命令操作 exec.Command,可以用来快速启动子进程并获取其输入输出
    buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput() 
    

三、实践练习例子

  • 猜谜游戏

    • 随机数的用法math/rand包):设置随机数种子

      maxNum := 100
      rand.Seed(time.Now().UnixNano())
      secretNumber := rand.Intn(maxNum)
      
    • 读取用户输入(使用bufio

      reader := bufio.NewReader(os.Stdin)
      input, err := reader.ReadString('\n')
      input = strings.Trim(input, "\r\n")
      
  • 在线词典

    • 发送http请求:在请求比较复杂的情况下用代码构造很麻烦,可以直接在浏览器中copy as curl,再在curlconverter.com中粘贴,即可得到代码。

      • 创建HTTP client:client := &http.Client{}
      • 将结构体转换成json,再转换为流作为body:var data = bytes.NewReader(buf) //buf为json
      • 生成request:req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data),其中第一个参数是http方法,第二个参数是url,第三个参数是body,只支持流式发送。
      • 设置请求的header:req.Header.Set()
      • 执行请求:resp, err := client.Do(req)
      • 为了避免资源泄露,在函数结束后关闭response的body:defer resp.Body.Close()
    • 解析response body:返回的json需要反序列化到结构体中,自定义结构体比较麻烦且容易出错,使用oktools.net/json2go来实现。

    • 注意在解析resp之前,首先要检查状态码是否正常(200),否则可能出错。

      if resp.StatusCode != 200 {
      	log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
      }
      
  • SOCKS5代理

    • 实现TCP echo server

      • 监听端口:server, err := net.Listen("tcp", "127.0.0.1:1080")
      • 接收请求:client, err := server.Accept()
      • 进行处理:go process(client),其中go关键字类似于开启一个子线程去处理这个过程。
      • 完成服务后关闭连接:defer conn.Close()
      • 使用nc命令连接服务器:nc 127.0.0.1 1080
    • 实现代理服务器的过程中常用的函数

      • 获得一个输入流:bufio.NewReader(conn)

      • 读取一个字节:reader.ReadByte()

      • 读取多个字节:method := make([]byte, methodSize) io.ReadFull(reader, method)

      • 写多个字节:conn.Write(...)(参数类型为[]byte)

      • 将输入流传到输出流:io.Copy(writer,reader)

      • 连接服务器:dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))

      • 使用context取消go routine的执行

        // 新建一个上下文
        ctx := context.Background()
        // 在初始上下文的基础上创建一个有取消功能的上下文
        ctx, cancel := context.WithCancel(ctx)
        // 发生取消事件
        cancel()
        // 监听取消事件
        <-ctx.Done()