Go 语言基础 - 基础语法

一、简介

1.1 什么是 Go 语言

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

二、Go 语言入门

2.1 安装 Golang

  1. 下载 Go https://go.dev/
  2. 配置 Go 语言开发环境 VScode https://go.dev/ 或 Goland https://www.jetbrains.com/zh-cn/go/

2.2 基础语法

2.2.1 Hello World

1
2
3
4
5
6
7
8
9
package main

import (
"fmt"
)

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

命令行执行

2.2.2 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"fmt"
"math"
)

// go 的常见变量类型包括 字符串 整数 浮点型 布尔型 等
// go 语言中, 大部分运算符的使用和优先级都和 C 或 C++ 类似
func main() {

//两种变量声明方式其一: var 变量名 数据类型 = 变量值
var a = "initial" //go 一般会自动推导变阿玲类型, 有需要的话也可以写上
//字符串类型 string 要用 "" 引起来

var b, c int = 1, 2 //同时声明两个整型, 写了数据类型

var d = true //布尔型, 没写数据类型

//未指定类型定义浮点型时, 模式类型是 float64
var e float64 //声明了一个浮点型变量 e, 但是没有赋值

//两种变量声明方式其二: 变量名 := 变量值
f := float32(e) //定义一个 float32 类型, 将变量 e 强转

g := a + "foo" //go 语言中字符串是内置类型, 可以直接通过加号拼接, 也可以直接用等于号比较两个字符串
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple

//常量的声明就是把 var 改成 const
//go 语言中的常量没有确定的类型, 会根据使用的上下文来自动确定类型
const s string = "constant"
const h = 500000000
const i = 3e20 / h
//sin 表示正弦
fmt.Println(s, h, i, math.Sin(h), math.Sin(i)) //constant 500000000 6e+11 -0.28470407323754404 0.7591864109375384
}

2.2.3 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

func main() {

//go 语言中只有 for 循环
i := 1
//什么限制条件都不写, 表示一个死循环
for {
fmt.Println("loop")
break //循环途中可以使用 break 跳出
}
//经典 C 循环
for j := 7; j < 9; j++ {
fmt.Println(j)
}

for n := 0; n < 5; n++ {
if n%2 == 0 {
continue //可以使用 continue 跳出循环
}
fmt.Println(n)
}
for i <= 3 { //经典 C 循环的任意一段都可以省略
fmt.Println(i)
i = i + 1
}
}

2.2.4 if 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {

//go 语言中 if 后面没有小括号, 就算写了编译的时候也会被去掉
//其他的跟 C 一样
if 7%2 == 0 { //go 中的 if 必须加大括号, 不能将 if 中的语句与 if 写在同一行
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}

if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}

if num := 9; num < 0 { //表示先定义一个 num 变量赋值 9, 再执行 if else 语句, 相当于在 if 语句外的定义
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}

2.2.5 switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"time"
)

func main() {

a := 2
switch a { //switch 后面的变量名 并不需要括号
case 1:
fmt.Println("one") //go 中不加 break 也不会跑完所有 case, 只会执行相应的 case
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}

t := time.Now()
//go 中的 swtich 功能很强大, 可以使用任意的变量类型, 甚至可以取代 if else 语句
//如何取代 if else 语句
switch { //switch 后不加任何变量
case t.Hour() < 12: //在 case 中写条件分支
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
//以上的代码会比 if else 逻辑更加清晰
}

2.2.6 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {

//因为不能任意更改长度, 使用较少
var a [5]int //声明一个整型数组
a[4] = 100 //给数组其中一个变量赋值
fmt.Println("get:", a[2]) //get: 0
fmt.Println("get:", a[4]) //get: 100
fmt.Println("len:", len(a)) //len: 5

b := [5]int{1, 2, 3, 4, 5} //声明并初始化一个整型数组
fmt.Println(b) //[1 2 3 4 5]

var twoD [2][3]int //声明一个整型二位数组
for i := 0; i < 2; i++ { //for 循环初始化二维数组
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
//Println相当于输出了两个拼接的变量 先输出 2d: 再输出 twoD
fmt.Println("2d: ", twoD) //2d: [[0 1 2] [1 2 3]]
}

2.2.7 切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import "fmt"

func main() {

//切片相比数组可以任意更改长度, 有更多丰富的操作
s := make([]string, 3) //用 make 创建一个长度为 3 的 string 型切片
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3

//append(原数组, "追加值"), 可以使用 append 来给切片追加值
s = append(s, "d") //将 append 的结果赋值到原切片
//slice 的原理是一个切片存储了一个长度 + 一个容量 + 一个指向一个数组的指针, 执行 append 时如果容量不够, 会扩容并返回新的切片
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]

c := make([]string, len(s)) //指定长度为切片 s 的长度
copy(c, s) //将切片 s 拷贝到切片 c
fmt.Println(c) // [a b c d e f]

//不同于 Python, 不支持负数索引
fmt.Println(s[2:5]) // [c d e] 取出 2~5 的元素
fmt.Println(s[:5]) // [a b c d e] 取出 0~5 的元素
fmt.Println(s[2:]) // [c d e f] 取出 2~end 的元素

good := []string{"g", "o", "o", "d"} // 定义并赋值, 相当于一个数组, 但是也可以是 slice
fmt.Println(good) // [g o o d]
good = append(good, "d") // 追加值
fmt.Println(good) // [g o o d d]

}

2.2.8 map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func main() {
/*
map ~ 哈希 ~ 字典 ~ 键值对
是实际使用最频繁的数据结构
go 中的 map 是完全无序的, 遍历的时候既不会按照字母顺序也不会按照插入顺序, 而是随机顺序
*/
m := make(map[string]int) //使用 make 创建一个空 map, key 是 string, value 是 int
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) // map[one:1 two:2] map[one:1 two:2]
}

2.2.9 range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

func main() {
// 可以用 range 快速遍历 slice 或 map, 使代码更加简洁
nums := []int{2, 3, 4} // 定义一个数组 或 slice
sum := 0
/*
i 表示切片的角标, num 表示切片的值
可以这么理解, 一个切片有两个为定值 nums[i] = num
map 中的键值对也是需要两个变量, 一个表示 key, 一个表示 value
*/
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"} // 定义一个 map
for k, v := range m { // 注意写法, 中间是 :=
fmt.Println(k, v) // a A \n b B
}
for k := range m { // 只遍历 key
fmt.Println("key", k) // key a \n key b
}
}

2.2.10 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

// func 定义一个函数, 函数的数据类型是后置的
func add(a int, b int) int { // 两个数相加
return a + b
}

func add2(a, b int) int { // 两个数相加, 省略一个数据类型
return a + b
}

// go 中函数原生支持多个返回值, 实际业务逻辑代码中几乎所有的函数都能返回两个值, 第一个是真正的结果, 第二个是一个错误信息
// (v string, ok bool) 这个位置本来是函数的数据类型, 这里需要返回的是两个变量, 所以直接这样写, 如果没有返回值的话这部分直接省略即可
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

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

2.2.11 指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// 无效修改
func add2(n int) {
n += 2
}

// 使用指针, 有小修改
func add2ptr(n *int) { // 指针函数也是, 数据类型后置
*n += 2
}

// go 里的指针类似于 C 中的指针, 只不过操作更加有限
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n) // 加个取地址符 & 使类型匹配
fmt.Println(n) // 7
}

2.2.12 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

// 结构体是带类型的字段的集合
type user struct {
name string
password string
}

func main() {
a := user{name: "wang", password: "1024"} // 结构体初始化, 注意需要传入每个字段的初始值
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024" // 分开初始化
var d user // 定义一个 user 结构体变量, 然后分别初始化
d.name = "wang" // 相当于键值对, 可以只对一部分字段进行初始化
d.password = "1024"

fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}

// 拷贝的结构体修改, 开销大
func checkPassword(u user, password string) bool {
return u.password == password
}

// 借助指针实现对于结构体的修改, 避免结构体的拷贝开销
func checkPassword2(u *user, password string) bool {
return u.password == password
}

2.2.13 结构体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

// 定义 user 结构体
type user struct {
name string
password string
}

/*
从上一个例子中的普通函数, 修改为结构体方法
具体的代码修改是把第一个参数加上括号写到函数名称前面

func checkPassword(u user, password string) bool {
return u.password == password
}
*/
func (u user) checkPassword(password string) bool {
return u.password == password
}

/*
从上一个例子中的普通函数, 修改为结构体方法
具体的代码修改是把第一个参数加上括号写到函数名称前面

func checkPassword2(u *user, password string) bool {
return u.password == password
}
*/
func (u *user) resetPassword(password string) { // 修改 password 变量
u.password = password // 这个函数不需要返回值, 所有函数定义的时候没有声明返回值类型
}

func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048") // 目的是可以以这种形式调用方法 (类似面向对象)
fmt.Println(a.checkPassword("2048")) // true
}

2.2.14 异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"errors"
"fmt"
)

type user struct {
name string
password string
}

// go 的语言习惯是使用一个单独的返回值来传递错误信息
// 在函数定义中, 在函数的返回值类型中加一个 error, 就代表这个函数可能会返回错误
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil // 返回值 1, nil 代表零值, 相当于 null
}
}
/*
不同于 Java 与 C++ 中的异常处理
go 的处理方式可以很清晰地知道哪个函数返回了错误, 并且可以食用菌简单的 if else 来处理错误

函数实现的时候 return 需要同时 return 两个值
报错的话 return nil, error
没报错的话 return 结果, nil
说白了就是结果和 error 只能有一个, 另一个为空
*/
return nil, errors.New("not found") // 返回值 2, 报错 error
}

func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
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)
}
}

2.2.15 字符串操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"strings" // 以下常用字符串工具函数都来自这个包
)

func main() {
a := "hello"
// 判断一个字符串里是否包含另外一个字符串
fmt.Println(strings.Contains(a, "ll")) // true
// 字符串中字符计数
fmt.Println(strings.Count(a, "l")) // 2
// 判断字符串是否以 prefix 开头
fmt.Println(strings.HasPrefix(a, "he")) // true
// 判断字符串是否以 prefix 结尾
fmt.Println(strings.HasSuffix(a, "llo")) // true
// 查找某个字符串的位置, 从 0 开始 -1 表示不包含
fmt.Println(strings.Index(a, "ll")) // 2
// 连接多个字符串
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
// 重复多个字符串
fmt.Println(strings.Repeat(a, 2)) // hellohello
// 替换字符串中的字符, n 表示前 n 个字符串, n = -1 表示全替换
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
}

2.2.16 字符串格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type point struct {
x, y int
}

func main() {
s := "hello"
n := 123
p := point{1, 2}
// Printf 与 Println 都可以用, 分别是 C 与 Java中的格式
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}

// %v 可以打印任意类型的变量, 不需要区分数字字符串等
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}
// %+v 打印详细结果
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
// %+v 打印更详细结果
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
}

2.2.17 JSON 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"encoding/json"
"fmt"
)

// 将结构体每个字段的首字母大写表示公开字段, 这个结构体就可以用 JSON.marshal 序列化变成一个 JSON 字符串
// 默认序列化出来的字符串风格是大写字母开头而不是下划线, 可以在后面用 json tag 等语法修改输出的 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)
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") // 每个字段序列化后用 \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"}}
}

2.2.18 时间处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
// "2006-01-02 15:04:05" 是固定的, 输出一个这样格式的现在时间
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
// .Unix 获取时间戳
fmt.Println(now.Unix()) // 1648738080
}

2.2.19 数字解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"strconv" // 关于字符串和数字类型之间的转换方法都在这个包下
)

// 如果输入不合法, 以下所有函数都会输出 error
func main() {
// ParseFloat 解析一个字符串, 解析成 float64
// _ 表示 err
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

// ParseFloat 解析一个字符串, 解析成 int 10进制
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111

n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096

// Atoi 把一个十进制字符串转成数字
// iotA 把一个数字转换成字符串
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123

n2, err := strconv.Atoi("AAA") // 输入不合法
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}

2.2.20 进程信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"os"
"os/exec"
)

func main() {
// os.Args 得到程序执行时指定的命令行参数
// 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
}


三、实战

3.1 猜谜游戏

程序首先会生成一个介于 1 到 100 之间的随机整数, 然后提示玩家进行猜测

玩家每次输入一个数字, 程序就回告诉玩家这个猜测的值是高于还是低于随机数

猜对了就告诉玩家胜利并退出程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)

func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano()) // time.Now().UnixNano() 通过时间戳来生成一个随机数种子
// 使用 rand.Intn() 的时候要设置随机数种子, 否则每次都会生成相同的随机数序列
secretNumber := rand.Intn(maxNum) // 上限为 maxNum, 下限是 0 (默认), 就生成了一个 0 ~ 100 的随机数
// fmt.Println("The secret number is ", secretNumber)

fmt.Println("Please input your guess")
/*
每个程序执行的时候都会打开几个文件, stdin stdout stderr
stdin 文件可以使用 os.Stdin 得到
但是直接操作这个 stdin 文件不方便, 就用 bufio.NewReader 方法把文件转换成一个 reader 变量
*/
reader := bufio.NewReader(os.Stdin) // 转成一个只读的流, 这个流里有很多行
for { // 猜不出来就一直猜
input, err := reader.ReadString('\n') // 用 ReadString 方法读取流中的一行
if err != nil { // 读取流失败 -- 打印错误 退出循环
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\n") // ReadString 方法返回值包含结尾的换行符, 去掉

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 // 之道猜对了跳出循环
}
}
}


3.2 在线词典

用户可以在命令行中查询一个单词, 我们通过调用第三方 API 查询单词的翻译并打印出来

使用 Go 来发送 HTTP 请求, 解析 JSON, 并使用代码生成来提高开发效率

3.2.1 抓包

先找到一个第三方 API

https://fanyi.caiyunapp.com

  1. 随便输入一个单词
  2. 点击翻译按钮
  3. 按 F12 进入开发者工具
  4. 在 Network 项中找到 All
  5. 左侧边栏从下向上找到最近的 dict ( 上面的都是之前请求的, 最下面的是最新的 )
  6. 检查请求头 Headers 中的请求形式应为 POST

第三方 API

Payload 中是一个 JSON 代码段

source 表示要翻译的词

trans_type 表示的是从英文翻译到中文

Payload

Preview 中显示的是这个词的翻译等

Preview

3.2.2 代码生成

右键 dict 一次选择 copy -> copy as cURL (bash) 复制所有请求头到一个空文件中

右键这个

随便复制到一个空文件中

在这个网站中将拿到的 JSON 准换成 Go 语言

https://curlconverter.com/go/

JSON to Go

将转换完成的代码粘贴回编译器是可以正常运行的, 可以看到返回了一大串 JSON

生成代码运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

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

func main() {
// 创建一个 HTTP client, 创建的时候可以指定很多参数, 包括是否启用请求超时的 cookie 等 (这里没有设置是否超时)
client := &http.Client{}
// 将请求体设置为一个只读流
var data = strings.NewReader(`{"trans_type":"en2zh","source":"infer"}`)
/*
http.NewRequest 构造一个 HTTP 请求
第一个参数 http 的方法 POST, 第二个参数是 URL, 第三个参数是 body
由于 body 可能很大, 放在内存里会影响程序效率, 所以将 body 设置成一个只读流
*/
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置一堆请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求并得到 response
// response 有自己的 HTTP 状态码, response header 和 body
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// response 的 body 也是一个流, 为了避免资源泄露需要手动添加一个 defer 方法来关闭这个流
// defer 函数会在函数运行之后从下往上执行, 这里只有一个 defer 所有只执行这一个
defer resp.Body.Close()
// 使用 ioutil.ReadAll 来读取 response 的 body 流
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 得到整个 body 后用 print 打印出来
fmt.Printf("%s\n", bodyText)
}

3.2.3 生成 request body

go 语言中生成一段 JSON 常用的方式是先构造一个结构体, 结构体与 JSON 结构是一一对应的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

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

// 构造一个结构体, 用来解析 JSON
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"` // 其实这个字段用不到
}

func main() {
// 创建一个 HTTP client, 创建的时候可以指定很多参数, 包括是否启用请求超时的 cookie 等 (这里没有设置是否超时)
client := &http.Client{}
// 初始化结构体成员
request := DictRequest{TransType: "en2zh", Source: "good"}
// 序列化字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
// 将请求体设置为一个只读流
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"infer"}`)
/*
由于这里是字节数组, 所以不用 string.NewReader
因为我们请求的应该是用一个变量作为一个请求体, 如果是 string 的话就相当于直接写死了

使用 bytes.NewReader 来构造 request 的请求体
*/
var data = bytes.NewReader(buf)
/*
http.NewRequest 构造一个 HTTP 请求
第一个参数 http 的方法 POST, 第二个参数是 URL, 第三个参数是 body
由于 body 可能很大, 放在内存里会影响程序效率, 所以将 body 设置成一个只读流
*/
// 以下代码没变
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置一堆请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求并得到 response
// response 有自己的 HTTP 状态码, response header 和 body
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// response 的 body 也是一个流, 为了避免资源泄露需要手动添加一个 defer 方法来关闭这个流
// defer 函数会在函数运行之后从下往上执行, 这里只有一个 defer 所有只执行这一个
defer resp.Body.Close()
// 使用 ioutil.ReadAll 来读取 response 的 body 流
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 得到整个 body 后用 print 打印出来
fmt.Printf("%s\n", bodyText)
}

3.2.4 解析 response body

go 语言是强语言类型, 不建议直接用 map 从 body 中取值

更常用的方法是和 request 一样写一个结构体, 然后把返回的 JSON 反序列化到结构体里面

https://oktools.net/json2go

将 Preview 中的 JSON 字符串复制到上述网站

右键 -> copy value

一定要是 rc 开头的整个这个东西, 不然之后生成的结构体会有问题

复制 JSON

转换-展开 生成多个独立的结构体

转换-展开

转换-嵌套 生成一个单独的结构体

因为这个小项目不需要对数据进行单独的操作所以生成嵌套的即可

转换-嵌套

运行一下是这个效果, 已经可以将翻译的结果输出了

解析请求体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

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

// 构造一个结构体, 用来解析 JSON
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 {
} `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 []interface{} `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 main() {
// 创建一个 HTTP client, 创建的时候可以指定很多参数, 包括是否启用请求超时的 cookie 等 (这里没有设置是否超时)
client := &http.Client{}
// 初始化结构体成员
request := DictRequest{TransType: "en2zh", Source: "infer"}
// 序列化字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
// 将请求体设置为一个只读流
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"infer"}`)
/*
由于这里是字节数组, 所以不用 string.NewReader
因为我们请求的应该是用一个变量作为一个请求体, 如果是 string 的话就相当于直接写死了

使用 bytes.NewReader 来构造 request 的请求体
*/
var data = bytes.NewReader(buf)
/*
http.NewRequest 构造一个 HTTP 请求
第一个参数 http 的方法 POST, 第二个参数是 URL, 第三个参数是 body
由于 body 可能很大, 放在内存里会影响程序效率, 所以将 body 设置成一个只读流
*/
// 以下代码没变
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置一堆请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求并得到 response
// response 有自己的 HTTP 状态码, response header 和 body
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// response 的 body 也是一个流, 为了避免资源泄露需要手动添加一个 defer 方法来关闭这个流
// defer 函数会在函数运行之后从下往上执行, 这里只有一个 defer 所有只执行这一个
defer resp.Body.Close()
// 使用 ioutil.ReadAll 来读取 response 的 body 流
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 得到整个 body 后用 print 打印出来
// fmt.Printf("%s\n", bodyText)
// 定义一个大结构体变量
var dictResponse DictResponse
// 将 JSON 的值反序列化到这个新定义的变量中, 注意 & 这样才能写入结构体
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
// 使用最详细的方式打印
fmt.Printf("%#v\n", dictResponse)
}

3.2.5 打印结果

在一堆结果中截取我们想要的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main

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

// 构造一个结构体, 用来解析 JSON
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 {
} `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 []interface{} `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 main() {
// 创建一个 HTTP client, 创建的时候可以指定很多参数, 包括是否启用请求超时的 cookie 等 (这里没有设置是否超时)
client := &http.Client{}
// 初始化结构体成员
request := DictRequest{TransType: "en2zh", Source: "infer"}
// 序列化字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
// 将请求体设置为一个只读流
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"infer"}`)
/*
由于这里是字节数组, 所以不用 string.NewReader
因为我们请求的应该是用一个变量作为一个请求体, 如果是 string 的话就相当于直接写死了

使用 bytes.NewReader 来构造 request 的请求体
*/
var data = bytes.NewReader(buf)
/*
http.NewRequest 构造一个 HTTP 请求
第一个参数 http 的方法 POST, 第二个参数是 URL, 第三个参数是 body
由于 body 可能很大, 放在内存里会影响程序效率, 所以将 body 设置成一个只读流
*/
// 以下代码没变
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置一堆请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求并得到 response
// response 有自己的 HTTP 状态码, response header 和 body
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// response 的 body 也是一个流, 为了避免资源泄露需要手动添加一个 defer 方法来关闭这个流
// defer 函数会在函数运行之后从下往上执行, 这里只有一个 defer 所有只执行这一个
defer resp.Body.Close()
// 使用 ioutil.ReadAll 来读取 response 的 body 流
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
/*
相当于环日志编程
得到的 response 不一定使我们想要的那个 response
如果状态码不是 200, 就直接报错并打印出状态码以及报文
这样方便我们在出错后排查问题
*/
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
// 得到整个 body 后用 print 打印出来
// fmt.Printf("%s\n", bodyText)
// 定义一个大结构体变量
var dictResponse DictResponse
// 将 JSON 的值反序列化到这个新定义的变量中, 注意 & 这样才能写入结构体
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
// 使用最详细的方式打印
//fmt.Printf("%#v\n", dictResponse)
var word string // 定义一个变量 word
/*
由于上一段代码中输出的值并不都是我们想要的, 我们只想要音标以及翻译
所以我们使用 dictResponse.Dictionary.Prons.En 来获取英式音标, ictResponse.Dictionary.Prons.EnUs 获取美式音标
然后再通过 range 对 dictResponse.Dictionary.Explanations 字典中的翻译进行循环打印, 输出所有的翻译
*/
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}

3.4.6 优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package main

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

// 构造一个结构体, 用来解析 JSON
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 {
} `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 []interface{} `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) {
// 创建一个 HTTP client, 创建的时候可以指定很多参数, 包括是否启用请求超时的 cookie 等 (这里没有设置是否超时)
client := &http.Client{}
// 初始化结构体成员
request := DictRequest{TransType: "en2zh", Source: "infer"}
// 序列化字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
// 使用 bytes.NewReader 来构造 request 的请求体
var data = bytes.NewReader(buf)
// http.NewRequest 构造一个 HTTP 请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置一堆请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求并得到 response
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// response 的 body 也是一个流, 为了避免资源泄露需要手动添加一个 defer 方法来关闭这个流
defer resp.Body.Close()
// 使用 ioutil.ReadAll 来读取 response 的 body 流
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))
}
// 定义一个大结构体变量
var dictResponse DictResponse
// 将 JSON 的值反序列化到这个新定义的变量中, 注意 & 这样才能写入结构体
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() {
// os.Args 判断命令和参数的个数
if len(os.Args) != 2 { // 如果不是两个就报错, 提醒正确的运行格式
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1] // 如果是正确的就抓取第二个, 也就是我们需要翻译的单词
query(word) // 查询翻译
}

/*
到目前为止是需要命令行输入指令才能运行
后期可以试着优化一下直接读取键盘输入
*/

运行演示