[Go 语言入门指南 | 青训营笔记]

83 阅读11分钟

0.准备工作

  • Go官网下载电脑对应的Go环境
  • 在VS code中下载对应extension

截屏2023-05-16 17.20.02.png

1. 课程安排

  • 简介10分钟
  • 入门30分钟
  • 实战60分钟

2. 什么是Go语言

  1. 高性能,高并发: 不像其他语言一样,需要去寻找第三方库,只要用标准库
  2. 语法简单: 类似于C,但简化了,例如: 只有for loop
  3. 丰富的标准库: 稳定性
  4. 完善的工具链: 错误检查,代码补充提示等
  5. 静态编译: 不需要任何附加东西. 不像cpp: 要一堆.o
  6. 快速: 修改完一行代码, 一秒钟就编译完成
  7. 跨平台: 路由器上也可以运行
  8. 垃圾回收: 和Java类似,无需考虑内存释放

3. 哪些公司在用Go

字节跳动, Google, 腾讯, 美团等

为什么字节要用Go呢

最初用python, 性能问题换了Go

cpp不适合在线web服务

4. 开发

source code

基础语法

  1. Hello world
package main
 
import "fmt"//导入format包
 
func main(){
	fmt.Println("hello world!!!")
}

运行结果: 截屏2023-05-17 09.20.04.png

  1. 变量

两种声明类型

  • var a = "initial" or var b,c int = 1,2
  • f := a + "foo"
package main
 
import (
	"fmt"
	"math"
)
 
func main(){

	var a = "initial" 

	var b,c int = 1,2 
	
	var d = true

	var e float64

	f := a + "foo"

	fmt.Println(b,c,d,e,f)

	fmt.Println(math.Sin(e))


}

运行结果:

截屏2023-05-17 09.34.40.png

  1. if-else

和C差不多,区别在于:

  • if后面不需要括号
  • 不能写在同一行
  1. for-loop

可以不写条件,就是死循环。

三个条件可以省略任意一个。

可以使用continue关键字。

  1. switch

不需要break

可以不需要条件,相当于if语句

  1. 数组

不常使用,因为长度固定

  1. 切片(可变长度数组)

创建: make

加入元素:append

可以用copy()

可以用slice[2:5](不支持负数)

  1. map
m := make(map[string]int)
m["one"] = 1
delete(m, "one")

可以用ok判断元素是不是存在

  1. range
m := map[string]string{"a": "A", "b" : "B"}
for k ,v := range m {
        fmt.Println(k, v)//a A; b B
}
  1. 函数

声明参数的时候, 类型在变量名后面 一般来说函数返回两个值, 后面一个值通常是错误信息

package main

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 := add(1, 2)
	fmt.Println(res) // 3

	v, ok := exists(map[string]string{"a": "A"}, "a")
	fmt.Println(v, ok) // A True
}

func exists 是一个函数定义,它接受两个参数:mkm 是一个 map[string]string 类型的映射(键值对),k 是一个字符串类型的键。函数返回两个值:vok

函数的目的是检查给定的映射 m 中是否存在指定的键 k。如果键存在于映射中,函数将返回对应的值 v 和一个布尔值 ok,表示键的存在。如果键不存在,那么 v 的值将是 ""(空字符串),ok 的值将是 false

  1. 指针

比起C,操作有限

  1. 结构体

可以声明结构体方法

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
}
  1. 错误处理
package main

import (
	"errors"
	"fmt"
)

type user struct {
	name     string
	password string
}

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

func main() {
	u, err := findUser([]user{{"wang", "1024"}}, "xu")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(u.name) // wang

	if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
		fmt.Println(err) // not found
		return
	} else {
		fmt.Println(u.name)
	}
}

以上代码是一个简单的 Go 程序。它定义了一个 user 结构体,其中包含了 namepassword 字段。

findUser 函数接受一个 users 切片和一个 name 字符串作为参数,并返回一个指向 user 结构体的指针以及一个错误对象。

该函数的目的是在给定的用户切片 users 中查找指定名称的用户。它使用循环遍历 users 切片,比较每个用户的 name 字段是否与指定的 name 字符串匹配。如果找到匹配的用户,则返回该用户的指针和 nil 的错误值;如果未找到匹配的用户,则返回 nil 的指针和一个自定义的错误值 "not found"

main 函数中,我们首先调用了 findUser 函数来查找名为 "wang" 的用户。如果找到了该用户,我们将打印该用户的 name 字段;如果未找到用户,则打印错误信息。

接着,我们使用了一个更简洁的方式来调用 findUser 函数来查找名为 "li" 的用户。通过使用短声明方式 u, err := findUser(...),我们在同一行声明并赋值了 uerr 两个变量。在这个短声明的作用域内,我们再次使用 uerr 来打印用户的 name 字段或错误信息。

  1. string
package main

import (
	"fmt"
	"strings"
)

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
	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
}
  1. fmt
package main

import "fmt"

type point struct {
	x, y int
}

func main() {
	s := "hello"
	n := 123
	p := point{1, 2}
	fmt.Println(s, n) // hello 123
	fmt.Println(p)    // {1 2}

	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
}

%v代表了所有类型

  1. json
package main

import (
	"encoding/json"
	"fmt"
)

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

以上代码演示了在 Go 中使用 encoding/json 包进行 JSON 编码和解码的示例。

  1. 首先,我们定义了一个名为 userInfo 的结构体,它具有 NameAgeHobby 字段。

  2. main 函数中,我们创建了一个 userInfo 类型的变量 a,并初始化它的字段值。

  3. 接下来,我们使用 json.Marshal 函数将 a 编码为 JSON 字节切片。json.Marshal 将结构体转换为 JSON 格式的字节切片。如果编码过程中发生错误,我们会使用 panic 函数抛出异常。

  4. 我们通过 fmt.Printlnstring 函数打印了编码后的 JSON 字节切片和对应的字符串形式。注意,打印字节切片时输出的是整数切片的内容,而打印字符串形式则是符合 JSON 格式的字符串。

  5. 然后,我们使用 json.MarshalIndent 函数对结构体 a 进行 JSON 编码,生成具有缩进格式的 JSON 字节切片。第二个参数为空字符串,表示不使用前缀;第三个参数 "\t" 表示使用制表符缩进。我们打印了缩进格式的 JSON 字符串。

  6. 接下来,我们声明了另一个 userInfo 类型的变量 b

  7. 使用 json.Unmarshal 函数将 JSON 字节切片解码为 b 变量的值。json.Unmarshal 将 JSON 字节切片解析为指定类型的变量。如果解码过程中发生错误,我们同样使用 panic 函数抛出异常。

  8. 最后,我们使用 fmt.Printf 打印解码后的结构体 b%#v 格式化动词会以 Go 语法的形式输出结构体的字段和值。

猜谜游戏

游戏效果

截屏2023-05-17 14.11.00.png

第一部分:生成一个随机数

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	maxNum := 100
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)
}

问题: 总是生成一个相同的数字

解决方案: 增加一个时间戳

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano())
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)
}

第二步:解析用户的输入输出

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)
	input, err := reader.ReadString('\n')//操作流
	if err != nil {
		fmt.Println("An error occured while reading input. Please try again", err)
		return
	}
	input = strings.Trim(input, "\r\n")//去掉换行

	guess, err := strconv.Atoi(input)//转成int
	if err != nil {
		fmt.Println("Invalid input. Please enter an integer value")
		return
	}
	fmt.Println("You guess is", guess)
}

这种比scan复杂,但是之后的项目会用到

第三步: 实现逻辑判断

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.Trim(input, "\r\n")

		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//猜对了就break
		}
	}
}

命令行版本词典

运行效果:

截屏2023-05-17 14.44.20.png

涉及到的知识: http请求,Json,使用代码生成提高开发效率

第一步: 打开第三方翻译

彩云小译

打开检查后观察http的POST请求,我们要在GO实现这个请求

截屏2023-05-17 15.15.18.png

会发现代码极其复杂,我们课程里介绍一个简单的方式生成请求:

第二步:生成HTTP请求

  1. 复制刚才的请求,选copy cURL
  2. 这个网址,把请求转换成代码

生成如下代码

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

func main() {
	client := &http.Client{}//创建一个client,可以指定很多参数比如timeout,这里没有
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)//创建请求,这里的data不是字符串,而是个流
	if err != nil {
		log.Fatal(err)
	}
        //设置请求头
	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")
	resp, err := client.Do(req)//发送请求,如果这里断网了或者DNS解析失败,会有err
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()//因为返回的response是个流,为了防止数据泄露
	bodyText, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText)
}

转译导致的错误直接删掉就好

运行结果:一大串JSON

第三步: 实现变量输入,生成request body

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

第四步: 解析response body

将JSON反序列化进到一个struct中,一一对应。

  • 还是用到代码生成
  • 把preview中的json复制进去
  • 点击转换-嵌套

打印json结果

第五步: 打印结果,完善代码

因为上一步中大部分的输出我们不需要

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}

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

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)
	}
	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")
	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)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))//检测是不是正确的response
	}
	var dictResponse 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)
}

这段代码是一个简单的命令行程序,它通过调用彩云小译的 API 来查询英文单词的释义、发音和相关信息。

代码中定义了两个结构体类型,DictRequestDictResponse,用于请求和响应的 JSON 数据的解析。DictRequest 结构体定义了请求的参数,包括翻译类型、源语言和用户ID。DictResponse 结构体定义了响应的字段,包括词条的详细信息、发音、释义等。

main 函数中,首先检查命令行参数的数量,确保用户提供了一个单词作为参数。然后调用 query 函数,将用户提供的单词作为参数进行查询。

query 函数中,首先创建一个 HTTP 客户端并构建请求。请求的 URL 是彩云小译的词典 API 地址。然后将请求参数进行 JSON 序列化,并发送 POST 请求。请求头部包含了一些必要的信息,如 User-Agent、Content-Type 和授权令牌等。

接收到响应后,读取响应的内容,并将其解析为 DictResponse 结构体。然后打印单词的发音和释义等信息。

这个程序使用了第三方库 net/httpencoding/json 来进行 HTTP 请求和 JSON 解析。运行程序时,需要提供一个英文单词作为命令行参数。