这是我参与「第五届青训营」伴学笔记创作活动的第 1 天
一、本堂课重点内容
本节课程主要分为三个方面:
- Go 语言简介
- Go 语言开发入门,包括开发环境配置、基础语法、标准库
- Go 实战,包括三个实战项目
二、详细知识点介绍
-
Go语言开发环境搭建(已完成)
-
第一行代码:Hello World
package main // 程序为main包的一部分 import "fmt" // 导入标准包 func main(){ fmt.Println("Hello World!") }使用
go run直接运行,go build编译为二进制。 -
变量与常量
声明变量的几种方式:
//第一种:指定变量的类型,并且赋值 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 -
条件语句与循环语句
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。
-
数组与切片
数组的声明方式:
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]) -
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") -
range的使用
// 对于数组返回两个值:索引和对应位置的值 nums:= []int{2,3,4} for _, num := range nums{ //若不需要索引可以用_来表示 ... } // 对于map返回两个值:键和值 m := map[string]int{"one": 1,"two": 2} for k, v := range m{ ... } -
函数
go中返回值是后置的,允许有多个返回值。
带有指针的函数与c语言类似,使用&和*。
func add(m map[string]int, b string) (c int, ok bool){ c, ok = m[b] return c, ok } -
结构体
//结构体定义 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 } -
错误处理
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") } -
字符串操作
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 -
格式化输出
使用
fmt.Printf()可以实现类似于c语言的格式化输出,但可以直接使用%v来输出,不需要判断数据类型。使用
%+v,%#v等,可以得到更加详细的输出。 -
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) } } -
时间处理
常用的时间处理函数在
“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 -
数字解析
常用的函数在
“strconv”包中。//将字符串解析成数字 f, _ := strconv.ParseFloat("1,234", 64) //第二个参数表示精度 n, _ := strconv.ParseInt("1234", 10, 64) //第二个参数表示进制 n2, _ := strconv.Atoi("123") n3, err := strconv.Atoi("abc") //如果字符串不是一个数字,报错 -
进程信息
需要
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()
- 创建HTTP client:
-
解析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()
-
-