Day2
猜谜游戏
步骤一
生成一个随机数来进行猜谜。下面使用 rand.Intn 函数生成一个 0 到 maxNum 之间的随机整数,并将这个整数赋值给 secretNumber 变量。
func main(){
maxNum := 100
secretNumber := rand.Intn(maxNum)
}
步骤二
加入了一个时间戳,可以保证每次运行程序时生成的随机数不同。
rand.Seed(time.Now().Unixno())
步骤三
接下来实现用户的输入输出,但是要注意的是每次程序执行的时候都会打开几个文件,stdin,stdout,stderr等,但是直接操作这个文件很不方便,我们会用bufio。NewReader把文件转换一个reader变量,这个会有很多用来操作流的操作。我们可以用ReadString方法来读取一行,但是它返回的结果是包含结尾的换行符的,我们要将其去掉,在转换为数字。
func main(){
reader := bufio.NewReader(os.stdin)
input, err := reader.ReadString('\n')
if(err != nil){
fmt.println(err)
return
}
input = strings.Trim(input,"\r\n")
guess, err := strconv.Atoi(input)
if(err != nil){
fmt.println(err)
return
}
fmt.println(guess)
}
bufio.NewReader是从标准输入里面读取一行文本,并将其存储在 input 变量中。如果读取过程中发生错误,err 变量将被设置为非空值。
使用 strings.Trim 函数去除输入字符串 input 两端的回车符和换行符。
使用 strconv.Atoi 函数将去除了换行符的输入字符串转换为整数,并将其存储在 guess 变量中。如果转换失败,err 变量将被设置为非空值。
步骤四
实现判断逻辑,现在有了秘密的值,有了用户输入的值,比较两个值的大小,从而进行判断。
func main(){
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!")
}
}
步骤五
实现游戏循环将代码挪到一个for循环里面。
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
}
}
}
此为完整代码。
在线字典
抓包
首先是在网页里打开开发者模式,network(网络)中找到dict文档注意方法是post方法,另外就是查看预览和负载的部分可以看见内容。请求的header比较复杂,请求头是一个json格式里面有两个字段一个是你是从什么语言转化为什么语言,另一个是source就是你要查询的单词。api返回的结果会有Wiki和dictionary两个字段。
之后就是复制这个文件的header方式为cURL。
代码编写
这里导入了一些Go语言标准库中的包,以及一些第三方包,如log用于日志记录,http用于HTTP请求,ioutil用于文件读写,strings用于字符串处理。
func main(){
client := &http.Client{}//首先创建一个http.Client对象
//使用strings.NewReader函数创建一个io.Reader对象,包含要发送的json信息
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
//使用http.NewRequest函数创建了一个HTTP POST请求,请求的目标URL是彩云小译的API地址,请求体是准备好的数据。
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
}
之后发送一个httppost请求,读取并且打印响应的内容。
resp, err := client.Do(req) //http.Client的Do方法发送Http请求
if err != nil {
log.Fatal(err) //记录错误并且终止程序
}
defer resp.Body.Close()//这行代码使用defer关键字确保在函数返回之前关闭响应的主体,释放资源。
//这行代码使用ioutil.ReadAll读取响应的全部内容,并将内容和可能发生的错误分别赋值给bodyText和err。
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
Day3
Go语言入门-——工程实践
语言进阶
从并发角度了解Go高性能本质,首先要了解的是并发和并行
- 并发是指在一个时间段内多个程序在同一个处理机上运行
- 并行是指在同一时刻,多个任务在不同的处理器上执行,独立运行,不相互抢占资源
而Go可以充分发挥优势
Goroutine
协程:用户态,轻量级线程,栈KB级别。
允许程序在执行过程中主动暂停并稍后回复,且不是由操作系统内核直接管理,而是由程序员在代码中控制执行流。
协程调度完全由用户程序控制,从而在遇到I/O堵塞时不会阻塞整个进程,而是主动让出执行权。协程之间切换只是函数调用开销。
线程:内核态,线程跑多个协程,栈MB级别。
是操作系统进行运算调度的最小单位,是进程的一部分,每个线程独立执行不同任务。线程的调度由操作系统内核负责,通常采用时间片轮转的方式。线程在多核处理器上可以并行执行,但上下文切换需要内核介入,因此开销较大。
线程的切换涉及内核态和用户态的转换,因此开销相对较大。线程切换需要保存和恢复大量的上下文信息,包括寄存器、堆栈指针等。
CSP
提倡通过通信共享内存而不是通过共享内存而实现通信
Channal
func CalAquare(){
src := make(chan int)
dest := make(chan int 3)//3的意思是有着三个缓存空间
go func(){
defer close(src)
for i := 0 ; i < 10 ; i++{
src <- i //意思是i将值传递给src通过chan通道
}
}()
go func(){
defer close(dest)
for i := range src{
dest <- i * i
}
}()
for i := range dest{
Println(i)
}
}
创建两个无缓冲的 channel src 和 dest,dest 通道的容量为 3。 启动一个 goroutine,向 src 通道发送 0 到 9 的整数。 启动另一个 goroutine,从 src 通道接收整数,并将其平方后发送到 dest 通道。 主 goroutine 从 dest 通道接收平方值,并打印出来。
拓展巩固; 这里来讲述一下·defer·后文也会讲解,defer会实行栈一般的先进后出原则。
WaitGroup
是goland里的一个结构体类型用于实现并发控制,它能够使用一组gorountine的执行完毕。
通过Add,Done,Wait三个方法进行管理gorountione的等待与通知
- Add(delta int):增加或减少WaitGroup内部的计数器值。通常在启动goroutine之前调用,以设置需要等待的goroutine数量。
- Done():减少WaitGroup内部的计数器值,表示一个goroutine已经完成工作。通常在goroutine结束时调用。
- Wait():阻塞当前goroutine,直到WaitGroup内部的计数器值变为0,即所有被等待的goroutine都已完成。
依赖管理
依赖管理三要素
- 配置文件,描述依赖。 go.mod
- 中心仓库管理依赖库. Proxy
- 本地工具 go get/mod
其实可以和Java的maven类比一下。
依赖配置
首先是用路径来标识一个模块,从模块路径可以看出来从哪里找到模块。像前缀是github就表示可以从github仓库里寻找该模块。如果项目子包想被单独使用,那么则要通过init go.mod文件进行管理。下面是原生的sdk版本。再下面是单元依赖。
-version
go.mod定义了版本规则,分为
- 语义化版本
- 基于commit的伪版本
语义化版本表示不同的MAJOR版本是不兼容的API,MINOR通常是新增函数功能,向后兼容;patch版本一般是修复bug,
伪版本,前缀和语义化版本是类似的;时间戳也就是提交commit的时间,然后是校验码,包含12位哈希前缀,每次提交commit后Go都会默认生成一个伪版本号。
indirect
这个是依赖单元中的特殊标识符,表示go.mod对应当前模块,没有直接导入该依赖模块的包,是一种间接依赖。
A->B->C{A-B是直接 A-C是间接}
incompatible
主版本2+模块会在模块路径增加/vN后缀。
对于没有go.mod文件并在主版本2+的依赖,会被打上+icompatible的后缀。
依赖分发——回源
这里就是讲从哪里下载,如何下载的问题。
直接使用版本管理仓库下载依赖
- 无法保证构建稳定性——增加删除软件版本
- 无法保证依赖可用性——删除软件
- 增加第三方压力——代码平台负载问题
解决方案 go proxy是一个服务站点,会缓存=源软件版本不会改变,在源站软件删除后可依然使用,构建时,会直接从GO Proxy站点拉取依赖。
那么如何使用呢,让我们来讲述一下。
GOPROXY环境变量可以控制Go Proxy。GOPROXY是Go Proxy站点URL列表,可以使用direct表示源站。
go get工具
- @update——默认
- @none——删除依赖
- @v1.12——tag版本,语义版本
- @23dfdd5——特定的commit
- @master——分支最新的commit
go mod工具
- init——初始化,创建go.mod文件
- download——下载模块到本地缓存
- tidy——增加需要的依赖,删除不需要的依赖
测试
回归测试 ——>集成测试——>单元测试
以上三个覆盖率逐渐变大,成本逐渐降低
- 一般是QA同学通过中端回归一些主流程场景
- 回归测试是在对软件进行修改(如修复缺陷或添加新功能)后,重新运行之前的测试用例,以确保修改没有引入新的错误。
- 例如,如果在一个项目中修复了一个计算错误,回归测试会重新运行所有与该计算相关的测试用例。
- 对系统功能维度测试
-
集成测试是将多个软件模块组合在一起作为一个整体进行测试,以确保这些模块能够协同工作。
-
例如,在一个 Web 应用程序中,集成测试可能会测试数据库访问层、业务逻辑层和用户界面层之间的交互。
3.测试开发阶段,对单独的函数,模块做功能验证。
-
- 单元测试是对软件中的最小可测试单元进行检查和验证。在大多数编程语言中,最小可测试单元通常是函数或方法。
- 例如,在 Go 语言中,你可能会对一个简单的数学计算函数进行单元测试。