标题:Go语言上手-基础语言 | 青训营笔记
这是我参与「第三届青训营-后端场」笔记创作活动的第一篇
什么是Go语言
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
GO语言入门
开发环境-安装Golang
- Golang官网 go.dev 直接安装 或者 选择 studygolang.com/dl 或 goproxy.cn/
- 配置集成开发环境
- Vscode (跨平台,功能齐全,需要go插件)
- Golang(智能化,收费)
- 基于云开发环境
基础语法
第一个go程序-Hello World
package main
import (
"fmt"
)
func main(){
fmt.Println("hello world")
}
- 如何运行?
- 在项目所在位置 执行命令 : go run 程序路径
- 程序编译为可执行文件:go build 程序路径 编译完后直接双击运行可执行文件
变量
- Go是一门强类型语言,每个变量均有自己的数据类型
- 字符串类型是内置类型,可以用加号进行拼接,或者用等于号去比较字符串
变量的声明
- var 变量名 = 值
- 此方式自动推导数据类型
- 变量 := 值
常量
使用 const 进行声明
//Go的变量与常量
package main
import (
"fmt"
)
func main(){
//变量的声明
var a = "initial" //字符串
var b,c int = 1,2 //整型
var d = true //布尔型
var e float64
f := float32(4) //数据类型转化
g := a + "foo" //字符串拼接
fmt.Println(a,b,c,d,e,f,g)
//常量
const s string = "constant"
const h = 5000000
const i = 3e20 / h
fmt.Println(s,h,i)
}
if-else
注意点
- if 后面的判断条件没有括号
- 判断条件后面必须带大括号
循环
注意点
- 没有while与do-while,只有for循环
- break跳出本层循环,continue跳出本次循环
switch
注意点
- switch 后面的变量不需要括号
- 默认下不需要break来阻止继续执行其下面的case
- switch与case后面可以为任何类型,如变量,常量函数等
数组
- 声明数组 var 数组名[长度] 数据类型 如:var a[5] int
- 读取 数据名[index] 如:a[4]
- 获取长度 len(a)
- 因为数组的长度固定,所以开发中更多的使用切片
切片
-
切片是一个可变长度的数组,可以改变切片的长度
-
声明:切片名 := make([]数据类型,长度)
-
使用append进行追加元素,注意append的结果必须赋值给原数组
s := make([]string,3) s = append(s,"d") //[d] -
切片拷贝 copy(切片1,切片2)
-
切片取值
- s[2:5] 表示输出索引从2到4(包括2不包括5)的元素
- s[:5] 表示输出索引从0到4的元素
- s[2:] 表示输出索引从2 到 len(s)-1 的元素
map
- 创建:m : = make (map[string] int) 需要两个类型 string 为key的类型,int为value的类型
- 写入键值对: m[key] = value
- 读取值 m[key] 返回两个值,第一个为key对应的value,第二个为是否存在key
- 删除键值对 delete(m,key)
- map完全无序,遍历不会按照顺序输出,而是随机的顺序
range
-
常用于map和切片等的快速遍历,返回两个值,索引与对应的值
package main import "fmt" func main() { nums := []int{2, 3, 4} sum := 0 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"} for k, v := range m { fmt.Println(k, v) // b 8; a A } for k := range m { fmt.Println("key", k) // key a; key b } }
函数
- 注意变量类型需要后置
- 语法格式: func 函数名(变量1 数据类型)(返回值类型1){}
- 变量与返回值均可以为多个
指针
- 主要用途,对参数进行修改
- 指针类型 *数据类型
结构体
-
结构体是带类型的字段的集合
-
可以用结构体的名称初始化结构体的变量
//user为结构体名称 type user struct{ name string password string } a := user{name:"wang" , password:"1234"} -
可以用(.)去读取或写入结构体字段的内容 如user.name = "tom"
-
结构体可以作为函数的参数,如果使用指针可以对结构体进行修改,并且节省大结构体的开销
结构体方法
-
可以为结构体定义一些方法
-
格式 func (结构体名 数据类型) 方法名 (变量 数据类型){}
//可以选择带指针或者不带指针 func (u *user) resetPassword(password string) { u.password = password }
错误处理(error)
- 通常用返回值来接收错误
- 在函数中,可以在返回值类型传入一个error 用来接收错误
- 创建错误 error.New() 可以输出错误的提示语句等
字符串操作
- 标准库strings
- 常用炒作
- Contains判断一个字符串中是否包含另一个字符串
- Count字符串计数某字符出现次数
- HasPrefix判断以某某开头
- Index查找某字符串位置
- Join连接多个字符串
- Repeat重复多个字符串
- Repalce替换字符
- len获取长度
- 对于中文而言,可能一个中文会对应多个字符
字符串格式化
- Println(value1,value2 ······) 打印多个值并换行
- Printf()格式化输出
- %v 打印任意类型变量(值)
- %+v 打印内部详细结构(字段名与值)
- %#v 打印更详细结构(结构体的类型名称以及字段名与值)
- %.2f保留两位小数的浮点数
JSON操作
-
导包 encoding/json
-
结构体中字段名首字母大写,可以用json.Marshal进行序列化成为一个byte数组 需转为字符串输出
buf, err := json.Marshal(a) fmt.Println(string(buf)) // {"Name":"hong","age":18,"Hobby":["Golang","TypeScript"]} -
反序列化 json.Unmarshal
err = json.Unmarshal(buf, &b) -
自定义字段名 ‘json:"别名"’
Age int `json:"age"`
时间处理
- 导包 time
- 当前时间 now:= time.Now()
- 格式化时间 time.Fotmat("2006-01-02 15:04:05") 需要用特定时间作为格式 "2006-01-02 15:04:05"
- 解析成时间 time.Parse("2006-01-02 15:04:05",time)
- 时间戳 now.Unix()
数字解析
- 导包 strconv
- 解析字符串 Parseint(字符串,进制,返回位数) 注:如果进制为0则自行推测
- 使用 strcov.Atoi 快速把一个字符串转为数字 返回值:数字,错误
进程信息
- 导包 os 与 os/exec
- os.Args获取进程进行的命令行参数
- os.Getenv 获取环境变量
- os.Setenv 写入环境变量
- exec.Command 快速启动子进程
实战
猜谜游戏
介绍
由程序生成0-100的随机数,由用户来进行猜测,程序返回输入的数字是大于小于或等于随机数,直到猜对为止
编写程序
第一步
生成0-100的随机数,使用math/rand包
package main
import(
"fmt"
"math/rand"
)
func main(){
maxNum := 100
secretNumber := rand.Init(maxNum) //生成随机数
}
-
注意此时每次创建的随机数都是相同的数
-
需要使用随机数种子来改变随机数
package main import( "fmt" "math/rand" "time" ) func main(){ maxNum := 100 //时间戳,精确到纳秒,使随机数改变更快速 rand.Seed(time.Now().UnixNano()) secretNumber := rand.Init(maxNum) //生成随机数 }
-
第二步
获取用户输入并且解析为数字
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.TrimSuffix(input, "\n")
//解析为数字,得到用户输入的数字
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
fmt.Println("You guess is", guess)
第三步
比较值的大小并且提示用户结果,直到猜对了
- 所有猜数,生成随机数,读取用户输入均写在for循环中,直到用户猜对就break退出循环结束程序
- 如果用户输入错误,则输出错误提示并且跳出当次循环,继续猜数
在线词典
介绍
实现一个命令行版本的在线词典即在命令行输入一个单词,打印输出单词的含义等信息
- 使用的api
- 抓包 fanyi.caiyunapp.com/ 右键网页页面检查
- 随意输入一个单词,点击翻译按钮,在开发中页面的网络中找到POST请求(dist包)
- 点击此包的负载和预览查看信息,即为单词对应的信息
- source表示输入的英文,trans_type为翻译的中文
- 需要实现此功能,必须在Go中去发送请求
编写程序
第一步:生成请求--代码生成
-
右击浏览器的请求包(比如dist包) 点击复制,选择复制为cURL(bash)
-
找一个终端(或记事本)去粘贴请求,为json格式
-
代码生成网址 curlconverter.com/#go 打开之后,粘贴复制的请求,在语言选择Go,复制生成的代码,删掉一些编译错误的几行代码即可
-
解读生成代码
package main import ( "fmt" "io/ioutil" "log" "net/http" "strings" ) func main() { client := &http.Client{} //创建的data使用request生成 var data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`) //创建请求,POST请求,url,data(一个流,减少内存的占用) req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) if err != nil { log.Fatal(err) } //设置请求头 req.Header.Set("Accept", "application/json, text/plain, */*") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") req.Header.Set("Connection", "keep-alive") req.Header.Set("Content-Type", "application/json;charset=UTF-8") req.Header.Set("Origin", "https://fanyi.caiyunapp.com") req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") 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/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39") req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") req.Header.Set("app-name", "xy") req.Header.Set("os-type", "web") req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Microsoft Edge";v="101"`) req.Header.Set("sec-ch-ua-mobile", "?0") req.Header.Set("sec-ch-ua-platform", `"Windows"`) //发起请求,返回一个响应 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) }
第二步:生成request body
用户需要用字符串输入,而不是用json输入,构造一个结构体,初始化结构体
type DictRequest struct {
TransType string `json:"trans_type"`//翻译结果
Source string `json:"source"` //需翻译内容
UserID string `json:"user_id"`
}
func main(){
request := DictRequest{TransType: "en2zh", Source: "good"}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
//承接第一步
}
第三步:解析response body
生成的response为json格式,需要进行反序列化
写一个结构体,结构体保存输出的内容,将response反序列到结构体的字段名中并输出
为了减少繁琐以及出错
- 使用代码生成
- 打开 oktools.net/json2go
- 复制dist包的响应中的json字符串到网站中
- 点击转化-嵌套生成对应的结构体,点击复制
//承接第二步
//定义结构体变量
var dictResponse DictResponse
//反序列化
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
//打印输出response结构体
fmt.Printf("%#v\n", dictResponse)
第四步:打印结果
提取需要的字段并打印输出
打印结构体中需要的字段名
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
//打印状态码,如果不是正常的(非200)打印出状态码方便解决出现的问题
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
最后提取方法即可
SOCKS5代理服务器
介绍
socks5协议相当于在防火墙开了一个口子,方便用户通过单个端口访问内部资源
运行程序之后,在浏览器配置socks,当访问某个网页时,会输出网页的请求
也可以执行命令 curl --socks5 ip:port -v url 注明:ip:IP地址 port:端口号 url:访问的地址
socks协议原理
- 浏览器和socks5代理服务器建立连接,然后代理服务器与真正的服务器建立连接
- 代理服务器与真正的服务器建立连接四个阶段
- 协商阶段 代理服务器选择一个支持认证的方式返回给服务器,告诉浏览器用什么建程方式
- 认证阶段
- 请求阶段 请求建立连接
- relay阶段 代理服务器收到请求到转到真正的服务器
编写程序
第一步:TCP echo server
测试:给服务器发生就响应返回对应的内容
package main
import (
"bufio"
"log"
"net"
)
func main() {
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err)
}
for {
client, err := server.Accept()
if err != nil {
log.Printf("Accept failed %v", err)
continue
}
go process(client)
}
}
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
b, err := reader.ReadByte()
if err != nil {
break
}
_, err = conn.Write([]byte{b})
if err != nil {
break
}
}
}
//测试,执行命令nc IP地址 端口号 如 nc 127.0.0.1 1080
//输入内容返回对应的相同内容
第二步:auth-认证阶段
选择建程方式0x00不需要认证
第三步:请求阶段
第四步:relay阶段
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()
浏览器测试
- 安装插件 SwitchyOmega
- 点击新建情景模式,代理协议为socks5
- 选择应用选项