青训营X豆包MarsCode 技术训练营第二课 | 豆包MarsCode AI 刷题

209 阅读9分钟

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高性能本质,首先要了解的是并发和并行

  1. 并发是指在一个时间段内多个程序在同一个处理机上运行
  2. 并行是指在同一时刻,多个任务在不同的处理器上执行,独立运行,不相互抢占资源

而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都已完成。

依赖管理

依赖管理三要素

  1. 配置文件,描述依赖。 go.mod
  2. 中心仓库管理依赖库. Proxy
  3. 本地工具 go get/mod

其实可以和Java的maven类比一下。

依赖配置

首先是用路径来标识一个模块,从模块路径可以看出来从哪里找到模块。像前缀是github就表示可以从github仓库里寻找该模块。如果项目子包想被单独使用,那么则要通过init go.mod文件进行管理。下面是原生的sdk版本。再下面是单元依赖。

image-20241106103530327

-version

go.mod定义了版本规则,分为

  1. 语义化版本
  2. 基于commit的伪版本

语义化版本表示不同的MAJOR版本是不兼容的API,MINOR通常是新增函数功能,向后兼容;patch版本一般是修复bug,

伪版本,前缀和语义化版本是类似的;时间戳也就是提交commit的时间,然后是校验码,包含12位哈希前缀,每次提交commit后Go都会默认生成一个伪版本号。

image-20241106104406125

indirect

这个是依赖单元中的特殊标识符,表示go.mod对应当前模块,没有直接导入该依赖模块的包,是一种间接依赖。

A->B->C{A-B是直接 A-C是间接}

incompatible

主版本2+模块会在模块路径增加/vN后缀。

对于没有go.mod文件并在主版本2+的依赖,会被打上+icompatible的后缀。

依赖分发——回源

这里就是讲从哪里下载,如何下载的问题。

直接使用版本管理仓库下载依赖

  • 无法保证构建稳定性——增加删除软件版本
  • 无法保证依赖可用性——删除软件
  • 增加第三方压力——代码平台负载问题

解决方案 go proxy是一个服务站点,会缓存=源软件版本不会改变,在源站软件删除后可依然使用,构建时,会直接从GO Proxy站点拉取依赖。

image-20241106111155698

那么如何使用呢,让我们来讲述一下。

GOPROXY环境变量可以控制Go Proxy。GOPROXY是Go Proxy站点URL列表,可以使用direct表示源站。image-20241106111450494

go get工具

  • @update——默认
  • @none——删除依赖
  • @v1.12——tag版本,语义版本
  • @23dfdd5——特定的commit
  • @master——分支最新的commit

go mod工具

  • init——初始化,创建go.mod文件
  • download——下载模块到本地缓存
  • tidy——增加需要的依赖,删除不需要的依赖

测试

回归测试 ——>集成测试——>单元测试

以上三个覆盖率逐渐变大,成本逐渐降低

  1. 一般是QA同学通过中端回归一些主流程场景
    • 回归测试是在对软件进行修改(如修复缺陷或添加新功能)后,重新运行之前的测试用例,以确保修改没有引入新的错误。
    • 例如,如果在一个项目中修复了一个计算错误,回归测试会重新运行所有与该计算相关的测试用例。
  2. 对系统功能维度测试
  • 集成测试是将多个软件模块组合在一起作为一个整体进行测试,以确保这些模块能够协同工作。

  • 例如,在一个 Web 应用程序中,集成测试可能会测试数据库访问层、业务逻辑层和用户界面层之间的交互。

    3.测试开发阶段,对单独的函数,模块做功能验证。

    • 单元测试是对软件中的最小可测试单元进行检查和验证。在大多数编程语言中,最小可测试单元通常是函数或方法。
    • 例如,在 Go 语言中,你可能会对一个简单的数学计算函数进行单元测试。