「Go 语言上手-基础语言」
这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
1. 简介
1.1 什么是Go语言?
Go语言特点
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
示例:实现一个简单的http服务器(承载静态文件访问,支持高性能高并发)
package main
import(
"net/http"
)
func main(){
//在标准库的http包里内建了路由,把"/"指向一个静态文件,实现对静态文件的访问
http.Handle("/",http.FileServer(http.Dir(".")))
//监听8080端口,并启动服务器
http.ListenAndServe(":8080",nil)
}
两行代码就实现了服务器端访问静态页面的任务,高性能
2 Go入门
2.1 开发环境
安装Go
官网链接:go.dev/
配置集成开发环境(VS code、Goland)
基于云的开发环境
可在线打开课程的示例项目,开始编码
2.2 基础语法
HelloWorld示例实现
//程序的入口包
package main
//导入标准库里的fmt包,向屏幕输入输出字符串,格式化字符串
import(
"fmt"
)
func main(){
//调用函数
fmt.Println("hello world")
}
变量
-
golang是一门强类型语言
-
常见:字符串、整数、浮点数、布尔型
-
变量两种声明方式:
var 变量名 = 变量值,此时主动推断变量类型变量名 := 变量值
-
常量声明:
const 常量名 = 常量值,同样根据上下文自动确定类型
package main
import(
"fmt"
"math"
)
func main(){
//变量声明的两种方式:
//1. var 变量名 = 变量值,此时主动推断变量类型
var a = "initial"
//也可以显式地写出变量类型
var b,c int = 1,2
var d = true
var e = float64
//2. 变量名 := 变量值
f := float32(e)
g := a + "foo"
fmt.Println(a,b,c,d,e,f)//initial 1 2 true 0 0
fmt.Println(g) //initialapple
//常量声明
//const 常量名 = 常量值,同样可以根据上下文自动确定类型
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s,h,i,math.Sin(h),math.Sin(i))
}
分支结构 if else
package main
import "fmt"
func main(){
//if后不跟(),且不可省略{},if后的语句不能写到同一行
if 7%2 == 0 {
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 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
分支结构 switch
- 无需加break,自动跳出分支
- case后支持多种表达形式,可加条件分支语句,避免if-else多层嵌套
package main
import (
"fmt"
"time"
)
func main(){
a := 2
switch a {
case 1:
fmt.Println("one")
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()
//可以在case后面跟条件语句
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
循环结构 for
- 有且只有for循环
package main
import "fmt"
func main(){
i := 1
for{
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
数组
-
一维数组两种声明方式:
var 数组名 [长度]类型,可以不赋初值数组名 := [长度]类型{数组元素值},要赋初值
package main
import "fmt"
func main(){
//一维数组两种声明方式
//1. var 数组名 [长度]类型
var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))
//2. 数组名 := [长度]类型{数组元素值}
b := [5]int{1,2,3,4,5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ",twoD)
}
切片(可变长度数组)
-
切片的声明方式:
切片名 := make([]元素类型,切片初始长度)切片名 := []元素类型{元素值}
-
可以像数组一样,通过访问下标直接访问,并赋值
-
通过
append()方法动态添加,注意:必须将添加元素后的新切片重新赋值回原切片 -
copy()方法,实现克隆 -
用
s[start:end]、s[:end]、s[atart:]表达式,取出指定范围内元素
package main
import "fmt"
func main(){
//1.切片的声明方式:
// -----切片名 := make([]元素类型,切片初始长度)
s := make([]string, 3)
// -----切片名 := []元素类型{元素值}
good := []string{"g","o","o","d"}
fmt.Println(good) //[g o o d]
//2.可以通过访问下标直接赋值
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
//3. 通过append()方法动态填充,注意:必须将添加元素后的新切片重新赋值回原切片
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)//[a b c d e f]
c := make([]string, len(s))
//4. copy()方法实现克隆
copy(c,s)
fmt.Println(c) //[a b c d e f]
//5. 取出指定范围内元素
fmt.Println(s[2:5])//[c d e]
fmt.Println(s[:5]) //[a b c d e]
fmt.Println(s[2:]) //[c d e f]
}
map
-
map的声明方式:
map名 := make(map[key类型]value类型)map名 := map[key类型]value类型{k1:v1, k2:v2}var map名 = map[key类型]value类型{k1:v1, k2:v2}
-
赋值类似数组,通过[]访问key值直接赋值,访问不存在key对应的value,返回初始值0
-
r, ok := map名[key]查询当前key是否存在 -
delete(map名,key)方法删除key-value对
package main
import "fmt"
func main(){
//map的声明方式
//1. map名 := make(map[key类型]value类型)
//2. map名 := map[key类型]value类型{k1:v1, k2:v2}
//3. var map名 = map[key类型]value类型{k1:v1, k2:v2}
m := make(map[string]int)
m2 := map[string]int{"one":1,"two":2}
var m3 = map[string]int{"one":1,"two":2}
fmt.Println(m2,m3)
//赋值类似数组,通过访问key值直接赋值
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["three"])//0
//r, ok := map名[key] 查询当前key是否存在
r, ok := m["unknow"]
fmt.Println(r,ok) //0 false
delete(m,"one")
}
range
-
用于进行快速遍历,类似迭代器
-
遍历数组,默认会返回两个值:下标和元素值
遍历map,默认返回两个值:key和value;若只返回一个,返回key
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"}
//对于map会返回两个值:key和value
//只返回一个时,返回key
for k,v := range m {
fmt.Println(k,v) //b B; a A
}
for k := range m {
fmt.Println("key",k) //key a; key b
}
}
函数
func 函数名(形参名 形参类型,形参名 形参类型) 返回值类型{}
-
形参及返回值变量,类型后置
-
一般函数都返回两个值,一是真正的函数运行结果,二是错误信息
-
多个返回值用() 括起来,有变量名
指针
-
用于对传入函数的参数值进行修改,默认是值传递机制,要想在函数里面修改外部变量,要创建一个指针指向原变量,将指针传入函数即可
-
带指针的形式传入参数就能对原参数进行修改
-
外部调用函数 &:
add2ptr(&n)被调用函数内部 :
func add2ptr(n *int) {}
结构体、结构体方法
-
带类型的字段的集合,类似对象
-
结构体声明:
type 结构体名 struct{字段名 字段类型 字段名 字段类型}
-
作为方法函数的参数传入时,有值传入和指针两种方式
-
结构体函数声明:
func (结构体实例名 结构体名) 函数名 (参数 参数类型) 返回值类型{} -
结构体方法 (类成员函数) 可以直接由
结构体实例名.方法名进行调用,相当于java类内部的方法
错误处理
- 采用单独的返回值来传递错误信息,能清楚地知道是哪个函数发生了错误,并使用简单的 if-else 进行处理
- 无错返回
nil
字符串操作、格式化
fmt.Printf("p=%v\n",p)打印结构体字段的值fmt.Printf("p=%+v\n",p)打印结构体的字段名称和值fmt.Printf("p=%#v\n",p)打印结构体的包名.结构体名、字段名称和值
JSON处理
- 保证结构体字段的首字母大写,则此结构体可用
buf,err := json.Marshal(a)序列化,再使用string(buf)即可显示成json形式字符串,后续使用json.Unmarshal()反序列化
时间处理
- 获取当前时间:
time.Now() - 时间的格式化
- 获取时间戳:
t3,err := time.Parse("2006-01-02 15:04:05","2022-03-27 01:25:36)其中,前一个基准时间是固定的
数字解析(数字 —— 字符串)
strconv.ParseFloat("1.234",64),可在参数中指明进制strconv.Atoi("123"),自动转换,输入非数字字符串会报错
获取进程信息
os.Args进程的命令行参数
3 实战演练
3.1 猜谜游戏
游戏开始,玩家每次输入一个数字,系统判断是大于小于还是猜中,循环往复,直到猜中
3.1.1 生成随机数(0-100)
version-1
package main
import (
"fmt"
"math/rand"
)
func main() {
maxNum := 100
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
- 问题: 发现每次输出的随机数都是一样的
- 解决: 设置随机数种子,获取程序每次启动的时间戳来初始化随机数种子
verson-2
func main() {
maxNum := 100
//初始化随机数种子
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
3.1.2 读取用户输入
- 直接用
os.Stdin不好处理,一般会用bufio.NewReader()将其转换为只读的流,对流进行处理 - 用
reader.ReadString("\n")从流中读取一行,注意: 会多读入一个换行符,需要去除 strconv.Atoi(input)实现字符串转数字 —— 报错!!!!- windows 系统和 Linux 系统的换行符不同,win10平台在按回车之后,会在参数的末尾加 \r\n 作为换行符,而代码只处理了\n,没有考虑\r\n
3.1.3 实现判断逻辑
version-4
- 玩家只能输入一次猜测
- 套循环
3.1.4 实现游戏循环
version-5
- 输入有误 continue,输入类型有误 continue ,猜测错误 继续循环,猜测正确 跳出循环
3.2 在线词典
一个命令行词典,可以在命令行进行调用,去查询一个单词,输出单词的音标及注释。
- 实现途径:调动第三方api进行查询,并输出
- 学习目的:学习如何用go语言发送http请求,解析json,如何使用代码生成来提高效率
3.2.1 抓包
-
打开第三方搜索网页(要求其响应体结构必须为json表达式):fanyi.caiyunapp.com/,输入单词进行查询,调出检查工具
-
在发送的dict请求里找POST请求,并对该请求 —> 右键 —> copy —> copy as cURL
curl 'https://api.interpreter.caiyunai.com/v1/dict' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json;charset=UTF-8' \
-H 'Origin: https://fanyi.caiyunapp.com' \
-H 'Pragma: no-cache' \
-H 'Referer: https://fanyi.caiyunapp.com/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H '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' \
-H 'X-Authorization: token:qgemv4jr1y38jyq6vhvi' \
-H 'app-name: xy' \
-H 'device-id: ' \
-H 'os-type: web' \
-H 'os-version: ' \
-H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Windows"' \
--data-raw '{"trans_type":"en2zh","source":"good"}' \
--compressed
3.2.2 代码生成
-
代码生成网页:curlconverter.com/#go
-
此时输入固定,要实现用户输入 ----- json序列化
3.2.3 生成 request body
- 构造一个请求结构体DictRequest,结构体内部字段名称与json字符串一一对应,然后直接调用
json.Marshal()方法即可实现
3.2.4 解析 response body
-
一般方法,定义结构体,其内字段与json字符串一一对应即可反序列化
-
采用代码生成工具:oktools.net/json2go,将浏览器检查工具里的请求的preview中的json字符串转化为结构体
-
创建响应结构体DictResponse,反序列化返回的json字符串
3.2.5 打印结果
- 选取需要的字段进行打印
- 注意:在接收到了响应之后,先检查响应状态码是否为200(成功)否则也不用往下了,并且开启日志便于调试
3.2.6 完善代码
- 将main函数改为query函数,传入需要查询的word参数,word从命令行获取,并事先进行部分验证