0.准备工作
- 去Go官网下载电脑对应的Go环境
- 在VS code中下载对应extension
1. 课程安排
- 简介10分钟
- 入门30分钟
- 实战60分钟
2. 什么是Go语言
- 高性能,高并发: 不像其他语言一样,需要去寻找第三方库,只要用标准库
- 语法简单: 类似于C,但简化了,例如: 只有for loop
- 丰富的标准库: 稳定性
- 完善的工具链: 错误检查,代码补充提示等
- 静态编译: 不需要任何附加东西. 不像cpp: 要一堆.o
- 快速: 修改完一行代码, 一秒钟就编译完成
- 跨平台: 路由器上也可以运行
- 垃圾回收: 和Java类似,无需考虑内存释放
3. 哪些公司在用Go
字节跳动, Google, 腾讯, 美团等
为什么字节要用Go呢
最初用python, 性能问题换了Go
cpp不适合在线web服务
4. 开发
基础语法
- Hello world
package main
import "fmt"//导入format包
func main(){
fmt.Println("hello world!!!")
}
运行结果:
- 变量
两种声明类型
var a = "initial" or var b,c int = 1,2f := 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))
}
运行结果:
- if-else
和C差不多,区别在于:
- if后面不需要括号
- 不能写在同一行
- for-loop
可以不写条件,就是死循环。
三个条件可以省略任意一个。
可以使用continue关键字。
- switch
不需要break
可以不需要条件,相当于if语句
- 数组
不常使用,因为长度固定
- 切片(可变长度数组)
创建: make
加入元素:append
可以用copy()
可以用slice[2:5](不支持负数)
- map
m := make(map[string]int)
m["one"] = 1
delete(m, "one")
可以用ok判断元素是不是存在
- range
m := map[string]string{"a": "A", "b" : "B"}
for k ,v := range m {
fmt.Println(k, v)//a A; b B
}
- 函数
声明参数的时候, 类型在变量名后面 一般来说函数返回两个值, 后面一个值通常是错误信息
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是一个函数定义,它接受两个参数:m和k。m是一个map[string]string类型的映射(键值对),k是一个字符串类型的键。函数返回两个值:v和ok。函数的目的是检查给定的映射
m中是否存在指定的键k。如果键存在于映射中,函数将返回对应的值v和一个布尔值ok,表示键的存在。如果键不存在,那么v的值将是""(空字符串),ok的值将是false。
- 指针
比起C,操作有限
- 结构体
可以声明结构体方法
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
}
- 错误处理
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 结构体,其中包含了 name 和 password 字段。
findUser 函数接受一个 users 切片和一个 name 字符串作为参数,并返回一个指向 user 结构体的指针以及一个错误对象。
该函数的目的是在给定的用户切片 users 中查找指定名称的用户。它使用循环遍历 users 切片,比较每个用户的 name 字段是否与指定的 name 字符串匹配。如果找到匹配的用户,则返回该用户的指针和 nil 的错误值;如果未找到匹配的用户,则返回 nil 的指针和一个自定义的错误值 "not found"。
在 main 函数中,我们首先调用了 findUser 函数来查找名为 "wang" 的用户。如果找到了该用户,我们将打印该用户的 name 字段;如果未找到用户,则打印错误信息。
接着,我们使用了一个更简洁的方式来调用 findUser 函数来查找名为 "li" 的用户。通过使用短声明方式 u, err := findUser(...),我们在同一行声明并赋值了 u 和 err 两个变量。在这个短声明的作用域内,我们再次使用 u 和 err 来打印用户的 name 字段或错误信息。
- 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
}
- 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代表了所有类型
- 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 编码和解码的示例。
-
首先,我们定义了一个名为
userInfo的结构体,它具有Name、Age和Hobby字段。 -
在
main函数中,我们创建了一个userInfo类型的变量a,并初始化它的字段值。 -
接下来,我们使用
json.Marshal函数将a编码为 JSON 字节切片。json.Marshal将结构体转换为 JSON 格式的字节切片。如果编码过程中发生错误,我们会使用panic函数抛出异常。 -
我们通过
fmt.Println和string函数打印了编码后的 JSON 字节切片和对应的字符串形式。注意,打印字节切片时输出的是整数切片的内容,而打印字符串形式则是符合 JSON 格式的字符串。 -
然后,我们使用
json.MarshalIndent函数对结构体a进行 JSON 编码,生成具有缩进格式的 JSON 字节切片。第二个参数为空字符串,表示不使用前缀;第三个参数"\t"表示使用制表符缩进。我们打印了缩进格式的 JSON 字符串。 -
接下来,我们声明了另一个
userInfo类型的变量b。 -
使用
json.Unmarshal函数将 JSON 字节切片解码为b变量的值。json.Unmarshal将 JSON 字节切片解析为指定类型的变量。如果解码过程中发生错误,我们同样使用panic函数抛出异常。 -
最后,我们使用
fmt.Printf打印解码后的结构体b,%#v格式化动词会以 Go 语法的形式输出结构体的字段和值。
猜谜游戏
游戏效果
第一部分:生成一个随机数
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
}
}
}
命令行版本词典
运行效果:
涉及到的知识: http请求,Json,使用代码生成提高开发效率
第一步: 打开第三方翻译
打开检查后观察http的POST请求,我们要在GO实现这个请求
会发现代码极其复杂,我们课程里介绍一个简单的方式生成请求:
第二步:生成HTTP请求
- 复制刚才的请求,选copy cURL
- 到这个网址,把请求转换成代码
生成如下代码:
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 来查询英文单词的释义、发音和相关信息。
代码中定义了两个结构体类型,DictRequest 和 DictResponse,用于请求和响应的 JSON 数据的解析。DictRequest 结构体定义了请求的参数,包括翻译类型、源语言和用户ID。DictResponse 结构体定义了响应的字段,包括词条的详细信息、发音、释义等。
在 main 函数中,首先检查命令行参数的数量,确保用户提供了一个单词作为参数。然后调用 query 函数,将用户提供的单词作为参数进行查询。
在 query 函数中,首先创建一个 HTTP 客户端并构建请求。请求的 URL 是彩云小译的词典 API 地址。然后将请求参数进行 JSON 序列化,并发送 POST 请求。请求头部包含了一些必要的信息,如 User-Agent、Content-Type 和授权令牌等。
接收到响应后,读取响应的内容,并将其解析为 DictResponse 结构体。然后打印单词的发音和释义等信息。
这个程序使用了第三方库 net/http 和 encoding/json 来进行 HTTP 请求和 JSON 解析。运行程序时,需要提供一个英文单词作为命令行参数。