GO语言笔记 | 青训营笔记

161 阅读5分钟

GO语言基础

1. Hello World

package main 		//main:程序的入口文件包
//在go中,应用程序的入口包要为main,而编译源码没有main包时,将无法编译输出可执行的文件,也就会导致错误

import "fmt"		//导入包:fmt

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

P.S.任何一个包中只能有一个Go文件带有main()函数

2. 变量

var a = "sky" //同js,根据后面的内容推断类型

	var b, c int = 1, 2 //在后面声明类型

	var e float64

	f := float32(e) //这相当于是强转把()

	g := a + "foo"	//字符串拼接
	fmt.Println(a, b, c, e, f)
	fmt.Println(g)

3. 条件判断

  1. 单条件判断

    if 7%2 == 0 {
    		fmt.Println("7 is even")
    	}
    
  2. switch-case

    • 不需要在每一个case后面写break
    • 判断变量类型可以是基本数据类型,也可以是字符串,结构体
    t := time.Now()				//判断当前系统时间
    
    	switch {
    	case t.Hour() < 12:
    		fmt.Println("上午")
    	default:
    		fmt.Println("下午")
    	}
    

4. 循环

i := 1 //初始化i=1
	for {
		fmt.Println("loop")	//通过break跳出循环
		break
	}

	for j := 7; j < 9; j++ {	//类似于java中的for
		fmt.Println(j)
	}

	for n := 0; n < 5; n++ {	//通过continue跳过一轮循环
		if n%2 == 0 {
			continue
		}
		fmt.Println(n)
	}

	for i <= 3 {				//相当于设置了boolean值,当i <= 3为true时循环继续
		fmt.Println(i)
		i++
	}

5. 数组

var a [5]int		//定义了一个长度为5的int数组
	a[4] = 100		
	
	fmt.Println(a[4], len(a))	//获取对应下标的数组元素和其数组长度

6. 切片(可变数组)

s := make([]string, 3) //创建切片
	s[0] = "a"
	s[1] = "b"
	s[2] = "c"

	s = append(s, "d") //给切片扩容

	c := make([]string, len(s))

	copy(c, s) //元素拷贝

	fmt.Println(s[1:3]) //输出下标1-3的元素
	fmt.Println(s[:5]) //输出下标0-4的元素(右值是取不到的) 

7. map

m := make(map[string]int) //key:string | value:int

	m["one"] = 1
	m["two"] = 2
	fmt.Println(m)
	fmt.Println(len(m))
	fmt.Println("-------")
	fmt.Println(m["one"])
	fmt.Println(m["unknow"])

	/**
	这是因为在Go语言中,当你尝试访问一个map中不存在的键时,它会返回该类型的零值。
	由于map的值类型为int,所以返回的零值为0。而ok变量返回的是一个布尔值,表示键是否在map中存在。
	ok是一个特殊的布尔变量
	由于"unknow"这个键并不存在于map中,所以ok的值为false。
	*/
	r, ok := m["unknow"]

	fmt.Println(r, ok)

	delete(m, "one")

8. Range

  • 当用于遍历数组、切片或字符串时,range会返回两个值:索引和元素的值。当用于遍历map时,range会返回两个值:键和值
arr := []int{1, 2, 3}
	for i, v := range arr {
		fmt.Println(i, v)	//i是索引,v是元素
	}

9. 函数

func add(a int, b int) int {
	return a + b
}

func main() {
	res := add(1, 2)
	fmt.Println(res)
}

10. 结构体

type user struct {
	name string
	pwd  string
}

func main() {
	a := user{name: "yx", pwd: "123"}
	b := user{"yx1", "123456"}
	c := user{name: "yx2"}
	c.pwd = "1024"

	fmt.Println(checkPwd(b, "123456"))

}

func checkPwd(u user, pwd string) bool {
	return u.pwd == pwd
}
  • 结构体方法

    type user struct {
    	name string
    	pwd  string
    }
    
    func (u *user) resetPwd(pwd string) {	//带指针可以对结构体进行修改
    	u.pwd = pwd
    }
    
    func main() {
    	a := user{name: "yx", pwd: "123"}
    	b := user{"yx1", "123456"}
    	c := user{name: "yx2"}
    	c.pwd = "1024"
    
    	fmt.Println(checkPwd(b, "123456"))
    
    	a.resetPwd("2048")
    	fmt.Println(a.pwd)
    }
    

11. 错误处理

func findUser(users []user, name string) (v *user, err error) {	//使用一个err返回错误信息
	for _, u := range users {
		if u.name == name {
			return &u, nil
		}
	}
	return nil, errors.New("not found")					//如果没有错误,直接new一个错误对象写上无错误即可
}

12. 字符串操作

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

13. 字符串格式化

s := "hello"
	n := 123
	p := point{1, 2}
	fmt.Println(s, n) // hello 123
	fmt.Println(p)    // {1 2}
	//跟C一样,用printf进行格式化
	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}

	f := 3.141592653
	fmt.Println(f)          // 3.141592653
	fmt.Printf("%.2f\n", f) // 3.14

14. JSON处理

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)			//用json包下的Marshal进行序列化
	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"}}
}

15. 时间处理

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

16. 字符串和数字转换

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")				//用Atoi进行快速转换
	fmt.Println(n2) // 123

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

17. 进程信息

// go run example/20-env/main.go a b c d				//输出一些进程信息
	fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
	fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
	fmt.Println(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

GO实战案例

1. 在线词典

1.1 步骤

image.png

image-20230512162254200.png

  • 代码生成

image-20230512162458230.png

  • 将复制的curl粘贴上去(P.S. 生成的代码可能会有错误,记得修改!)

image-20230512162859714.png

  • 代码解读

    // DictRequest 请求体来自请求负载的值
    type DictRequest struct {
    	TransType string `json:"trans_type"`
    	Source    string `json:"source"`
    	UserID    string `json:"user_id"`
    }
    
    
    func main() {
        //
    	client := &http.Client{}
    	request := DictRequest{TransType: "en2zh", Source: "good"}
    	//序列化结构体
    	buf, err := json.Marshal(request)
    	if err != nil {
    		log.Fatal(err)
    	}
    	var data = bytes.NewReader(buf)
    	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    	if err != nil {
    		log.Fatal(err)
    	}
        //通过前面curl获取到的请求头书写代码
    	req.Header.Set("Connection", "keep-alive")
    	req.Header.Set("DNT", "1")
    	req.Header.Set("os-version", "")
    	req.Header.Set("sec-ch-ua-mobile", "?0")
    	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
    	req.Header.Set("app-name", "xy")
    	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
    	req.Header.Set("Accept", "application/json, text/plain, */*")
    	req.Header.Set("device-id", "")
    	req.Header.Set("os-type", "web")
    	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
    	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
    	req.Header.Set("Sec-Fetch-Site", "cross-site")
    	req.Header.Set("Sec-Fetch-Mode", "cors")
    	req.Header.Set("Sec-Fetch-Dest", "empty")
    	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
    	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
    	req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
        //利用client的DO方法发送请求
    	resp, err := client.Do(req)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer resp.Body.Close()
    	bodyText, err := ioutil.ReadAll(resp.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("%s\n", bodyText)
    }
    
    在Go语言中,Client类型代表HTTP客户端。它的零值(DefaultClient)是一个可用的使用DefaultTransport的客户端。Client的Transport字段一般会含有内部状态(缓存TCP连接),因此Client类型值应尽量被重用而不是每次需要都创建新的。Client类型值可以安全的被多个go程同时使用。
    
    你可以使用Client类型的Do方法发送HTTP请求并获取响应。这个方法会遵守客户端设置的策略(如重定向、cookie、认证)。如果客户端的策略(如重定向)返回错误或存在HTTP协议错误时,本方法将返回该错误;如果回应的状态码不是2xx,本方法并不会返回错误。
    
  • 解析response Body(JSON转Golang Struct - 在线工具 - OKTools)

image-20230512171418809.png

  • 代码优化

    package main
    
    import (
    	"bytes"
    	"encoding/json"
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    )
    
    // DictRequest 请求体来自请求负载的值
    type DictRequest struct {
    	TransType string `json:"trans_type"`
    	Source    string `json:"source"`
    	UserID    string `json:"user_id"`
    }
    
    //根据上面生成的json->Go的反序列化代码生成
    type DictResponse struct {
    	Rc   int `json:"rc"`
    	Wiki struct {
    		KnownInLaguages int `json:"known_in_laguages"`
    		Description     struct {
    			Source string      `json:"source"`
    			Target interface{} `json:"target"`
    		} `json:"description"`
    		ID   string `json:"id"`
    		Item struct {
    			Source string `json:"source"`
    			Target string `json:"target"`
    		} `json:"item"`
    		ImageURL  string `json:"image_url"`
    		IsSubject string `json:"is_subject"`
    		Sitelink  string `json:"sitelink"`
    	} `json:"wiki"`
    	Dictionary struct {
    		Prons struct {
    			EnUs string `json:"en-us"`
    			En   string `json:"en"`
    		} `json:"prons"`
    		Explanations []string      `json:"explanations"`
    		Synonym      []string      `json:"synonym"`
    		Antonym      []string      `json:"antonym"`
    		WqxExample   [][]string    `json:"wqx_example"`
    		Entry        string        `json:"entry"`
    		Type         string        `json:"type"`
    		Related      []interface{} `json:"related"`
    		Source       string        `json:"source"`
    	} `json:"dictionary"`
    }
    
    //专门用一个query函数来接收需要查询的词
    func query(word string) {
    	client := &http.Client{}
    	request := DictRequest{TransType: "en2zh", Source: word}
    	buf, err := json.Marshal(request)
    	if err != nil {
    		log.Fatal(err)
    	}
    	var data = bytes.NewReader(buf)
    	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    	if err != nil {
    		log.Fatal(err)
    	}
        //通过前面curl获取到的请求头书写代码
    	req.Header.Set("Connection", "keep-alive")
    	req.Header.Set("DNT", "1")
    	req.Header.Set("os-version", "")
    	req.Header.Set("sec-ch-ua-mobile", "?0")
    	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
    	req.Header.Set("app-name", "xy")
    	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
    	req.Header.Set("Accept", "application/json, text/plain, */*")
    	req.Header.Set("device-id", "")
    	req.Header.Set("os-type", "web")
    	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
    	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
    	req.Header.Set("Sec-Fetch-Site", "cross-site")
    	req.Header.Set("Sec-Fetch-Mode", "cors")
    	req.Header.Set("Sec-Fetch-Dest", "empty")
    	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
    	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
    	req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
        //利用client的DO方法发送请求
    	resp, err := client.Do(req)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer resp.Body.Close()
    	bodyText, err := ioutil.ReadAll(resp.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//如果状态码不是200需要进行判断,方便状态诊断
    	if resp.StatusCode != 200 {
    		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
    	}
    
    	//初始化response结构体变量
    	var dictResponse DictResponse
        //将bodytext反序列化录入dictResponse结构体中
    	err = json.Unmarshal(bodyText, &dictResponse)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
    	for _, item := range dictResponse.Dictionary.Explanations {
    		fmt.Println(item)
    	}
    }
    
    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)
    }
    
    
    //os.Args是一个字符串切片,它包含了命令行参数。os.Args[0]是程序的路径,而os.Args[1:]包含了程序的命令行参数。
    

GO语言进阶

  • '快'
  • GO可以充分发挥多核优势,高效运行(在多个核心上的cpu运行)

1. Goroutine

1.1 协程与线程

  • 协程,用户态,轻量级线程,栈KB级别
  • 线程,内核态,线程中可以跑到多个协程,栈MB级别
func main() {
	HelloGoRoutine()
}

func hello(i int) {
	fmt.Println("hello goroutine " + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) { //利用go关键字创建协程
            //在第一次循环中,i的值为0,所以会创建一个新的协程来运行匿名函数,并将0作为参数传递给它。在这个协程中,匿名函数会调用hello函数,并将0作为参数传递给它。因此,第一个协程中调用的hello函数会接收到整数参数0。
			hello(j)	
		}(i)			
	}
	time.Sleep(time.Second)	//子协程在完成之前主线程就退出
}
这段代码的输出结果是不确定的,因为它依赖于协程的调度顺序。在每次循环中,都会创建一个新的协程来运行匿名函数,并将当前循环计数器i的值作为参数传递给它。这样,在每个协程中调用的hello函数都会接收到不同的整数参数。

然而,协程的调度顺序是不确定的,所以每个协程中调用的hello函数可能会以任意顺序执行。因此,这段代码的输出结果可能是任意顺序的整数。

在你给出的输出结果中,第一个协程中调用的hello函数接收到了整数参数4,所以它输出了hello goroutine 4。第二个协程中调用的hello函数接收到了整数参数0,所以它输出了hello goroutine 0。其余协程也是类似的。

1.2 CSP

  • Communicating Sequential Processes

  • Go提倡通过通道共享内存而不是通过共享内存而实现通信

image-20230513221143659.png

1.3 Channel

  • make(chan 元素类型,[缓冲大小【通道中能存放多少个元素】])
  • 无缓冲通道 make (chan int)
  • 有缓冲通道 make(chan int,2)

image-20230513221456417.png

src := make(chan int)
	dst := make(chan int, 3)

	go func() {
		defer close(src)	//defer 延迟后面的操作,这个函数会在包含它的函数返回之前被调用。
		for i := 0; i < 10; i++ { 	//将0-9存入src这个通道
			src <- i
		}
	}()

	go func() {
		defer close(dst)
		for i := range src {	//遍历src中的元素,将src通道中的元素平方后送进dst
			dst <- i * i
		}
	}()

	for i := range dst {		//输出平方
		//复杂操作
		println(i)
	}

1.4 并发安全 Lock

image-20230513223444341.png

  • 类似于OS中学习过的信号量实现临界区资源互斥,有多个进程并发执行,对同一资源(x)的竞争,所以我们通过对x进行加锁(Lock)来保证临界区资源的安全

    在并发编程中,多个协程可能会同时访问和修改同一个数据。如果不加以控制,这可能会导致数据不一致或其他错误。为了避免这种情况,我们需要使用锁来保证并发安全。
    
    锁是一种同步机制,它可以用来保护临界区,确保同一时间只有一个协程能够访问临界区中的数据。当一个协程需要访问临界区时,它必须先获取锁。如果锁已经被其他协程占用,那么这个协程就会被阻塞,直到锁被释放。当协程完成对临界区的访问后,它必须释放锁,以便其他协程能够获取锁并访问临界区。
    
    使用锁可以有效地避免多个协程同时修改同一个数据,从而保证数据的一致性和程序的正确性
    

1.5 WaitGroup

WaitGroup是Go语言中的一个结构体类型,它可以用来等待一组协程的完成。它提供了三个方法:AddDoneWait

  • Add方法用于增加等待计数。当你启动一个新的协程时,你应该调用Add(1)来增加等待计数。
  • Done方法用于减少等待计数。当一个协程完成时,它应该调用Done方法来减少等待计数。
  • Wait方法用于阻塞当前协程,直到等待计数变为0。当你需要等待所有协程完成时,你应该调用Wait方法。

下面是一个简单的例子,展示了如何使用WaitGroup来等待一组协程的完成:

var wg sync.WaitGroup

func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println(i)
        }(i)
    }
    wg.Wait()
}

在这个例子中,我们创建了一个WaitGroup变量wg。在for循环中,每次循环都会调用wg.Add(1)来增加等待计数,并启动一个新的协程。在每个协程中,都会调用wg.Done方法来减少等待计数。在for循环结束后,主协程调用wg.Wait()方法来阻塞,直到所有协程都完成。

image-20230513225225264.png

  • 分析:
    • wg.Add(5)因为创建了5个进程,所以初始等待数为5
    • 每当一个hello()函数执行完后,协程执行完毕,所以等待数-1
    • wg.Wait()等待所有子协程执行完毕,主进程才结束

GO依赖管理

  • GOPATH

image-20230513232848452.png

image-20230513232914744.png

  • GO VENDOR

image-20230513232901407.png

image-20230513233615190.png

GO MOD

image-20230513233806668.png

image-20230513234401465.png

image-20230513234429791.png

image-20230513234459623.png

依赖管理三要素

  • 配置文件,描述依赖 -> go.mod
  • 中心仓库管理依赖库 -> Proxy
  • 本地工具 -> go get/mod

GO语言测试

  • 回归测试:
  • 集成测试:对功能维度进行测试
  • 单元测试:测试开发阶段,开发者对某一功能进行测试

1. 单元测试

  • 单元:函数,模块,...
  • 优点:保证质量,提升效率
  • 规则:
    • 所有测试文件以_test.go结尾
    • func TestXxx(*testing.T)
    • 初始化逻辑放到TestMain方法中
  • 覆盖率
    • 1、
    • 2