go语言基础2 | 豆包MarsCode AI刷题

6 阅读5分钟

并发编程与测试

sync用于不需要通信的情况

var wg sync.WaitGroup
func download(url string) {
    fmt.Println("start to download", url)
    time.Sleep(time.Second) // 模拟耗时操作
    wg.Done()
}
for i := 0; i < 3; i++ {
	wg.Add(1)
	go download("a.com/" + string(i+'0'))
}
wg.Wait()
fmt.Println("Done!")
  • wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
  • go download():启动新的协程并发执行 download 函数。
  • wg.Wait():等待所有的协程执行结束。 如果需要通信我们需要使用channel 信道 make(chan string, 10)创建大小为 10 的缓冲信道
var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道
func download(url string) {
    fmt.Println("start to download", url)
    time.Sleep(time.Second)
    ch <- url // 将 url 发送给信道
}
for i := 0; i < 3; i++ {
    go download("a.com/" + string(i+'0'))
}
for i := 0; i < 3; i++ {
    msg := <-ch // 等待信道返回消息。
    fmt.Println("finish", msg)
}
fmt.Println("Done!")
// calc.go  
package main  
func add(num1 int, num2 int) int {  
	return num1 + num2  
}
// calc_test.go  
package main  
import "testing"  
func TestAdd(t *testing.T) {  
	if ans := add(1, 2); ans != 3 {  
		t.Error("add(1, 2) should be equal to 3")  
	}  
}

运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。 go test --cover命令可以查看测试的覆盖率 运行测试想要当测试文件所在的文件夹运行go test

PS E:\0Learn_to_code\Go\src\study> go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      study   0.109s

错误处理

defer会在函数退出时执行放入的命令,一个函数有多个defer时采用逆序的方式执行,也就是先执行最后定义的

func main() {  
	defer func() {  
		fmt.Println("defer func")  
	}()  
  
	arr := []int{1, 2, 3}  
	fmt.Println(arr[4])  
}

recover可以捕获panic防止程序异常退出

func test_recover() {  
	defer func() {  
		fmt.Println("defer func")  
		if err := recover(); err != nil {  
			fmt.Println("recover success")  
		}  
	}()  

	arr := []int{1, 2, 3}  
	fmt.Println(arr[4])  
	fmt.Println("after panic")  
}  

func main() {  
	test_recover()  
	fmt.Println("after recover")  
}

包与模块

一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。比如我们新建一个文件 calc.go, main.go 平级,分别定义 add 和 main 方法。就算add是小写的main也可也访问因为他们都是main包的

Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。

包名最好和文件夹的名字是一样的

性能建议

  • 使用make时尽可能预先分配内存,防止频繁的分配内存
  • 使用已有切片创建新切片时,会导致已有切片一直处于被应用状态,无法被释放尽量使用cope来实现
  • 对字符串操作时尽可能使用strings.Builder,因为在go中字符串是常量频繁的操作可以导致频繁分配内存,strings.Builder可以避免
  • go中空结构体struct{}是不占用空间的
  • 使用原子变量比加锁效率高,因为锁是通过操作系统实现的,而原子是通过硬件实现的

Gin

//使用了gin.Default()生成了一个实例,这个实例即 WSGI 应用程序。
r := gin.Default()
//声明一个路由
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Geektutu")
})
r.Run("0.0.0.0:1234") // 启动服务器,监听 0.0.0.0:1234

http的理由分为很多种

///user/gaslf进入这个
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
	c.String(200, "Hello, "+name)
})
//无参  /user进入这个
r.GET("/user", func(c *gin.Context) {
	c.String(200, "Hello, 无名之人")
})
///users?name=Tom&role=student带有参数的
r.GET("/users", func(c *gin.Context) {
	name := c.Query("name")  //查找name没有就返回空字符串
	role := c.DefaultQuery("role", "teacher") //查找role没有就返回默认值"teacher"
	c.String(200, "Hello, "+name+"! You are a "+role)
})

重定向

r.GET("/redirect", func(c *gin.Context) {
//当浏览器访问/redirect,浏览器会被重定向到`/index`,并且状态码为301,表示这是一永久的重定向
	c.Redirect(http.StatusMovedPermanently, "/index")
})
r.GET("/goindex", func(c *gin.Context) {
//当访问/goindex时会清空url再重新访问一次gin,这个性能比上面的消耗高
	c.Request.URL.Path = "/"
	r.HandleContext(c)
})

分组路由

// group routes 分组路由
defaultHandler := func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "path": c.FullPath(),
    })
}
//创建一个名为`v1`的路由分组,所有属于这个分组的路由都会以`/v1`为前缀。
v1 := r.Group("/v1")
{//这个花括号没有实际性的意义只是为了好看
	///v1/posts
    v1.GET("/posts", defaultHandler)
    v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
    v2.GET("/posts", defaultHandler)
    v2.GET("/series", defaultHandler)
}

自定义的中间件

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        // 给Context实例设置一个值
        c.Set("geektutu", "1111")
        // 请求前
        c.Next()//调用这个后将会等待请求处理完毕后再执行下面的
        // 请求后
        latency := time.Since(t)
        log.Print("哈哈哈哈我是中间件", latency)
    }
}
r.Use(Logger()) //全局的使用中间件
// 作用于单个路由  
r.GET("/benchmark", Logger(), benchEndpoint)
// 作用于某个组  
authorized := r.Group("/")  
authorized.Use(Logger())  
{  
	authorized.POST("/login", loginEndpoint)  
	authorized.POST("/submit", submitEndpoint)  
}

微服务框架

用户发起请求,先由负载均衡接收后给一个api网关,网关再使用rpc协议将数据给到响应的服务上

  • 服务注册 服务将自己注册到服务中心里
  • 服务发现 找到已经注册的服务
  • 服务发布 让一个服务升级运行新的代码

随机数的运用与理解

猜数字游戏往往需要生成一个随机的谜底数字。Go 语言中的math/rand包提供了生成随机数的功能,但要注意正确地初始化随机数生成器。这让我们深入理解了伪随机数的概念以及如何在程序中运用它们。例如,需要使用当前时间作为种子来初始化rand,以确保每次游戏开始时谜底数字都是不同的,增加游戏的可重复性和可玩性。

错误处理的必要性

在玩家输入猜测的过程中,可能会出现各种意外情况,如输入格式错误、超出范围等。妥善的错误处理可以保证程序的稳定性。使用deferrecover机制或者在输入读取阶段进行严格的格式检查,可以避免程序因用户的错误操作而崩溃。这让我们明白在任何程序中,都不能忽视对可能出现的错误情况的处理,就像为程序穿上了一层坚固的 “铠甲”,使其能在各种复杂的环境下正常运行。