2026-01-14
go
0

目录

http相关

小项目初步了解go

创建项目:

​ 创建一个文件夹(名字是golearn),进入文件夹,使用命令:go mod init golearn,这命令具体的执行内容后面再讲,最明显的是这个命令创建了一个go.mod文件,这个文件用于跟踪项目依赖的包,也能用于发布项目。

这个示例项目是创建一个网页,用于邀请大家来参加一个party,首先有一个欢迎界面,然后有一个表格界面用于填写要参加party的人的信息,以及对于填写了信息的人展示欢迎界面。

创建一个go文件(名字是learn1003.go):

​ 写入代码:

go
package main import "fmt" func main() { fmt.Println("TODO:add some features") }

main函数是整个项目的入口函数,每个项目有且仅有一个

自定义数据类型和集合

​ 创建一个RSVP请求数据类型:

go
package main import "fmt" type Rsvp struct{ Name,Email,Phone string WillAttend bool } func main() { fmt.Println("TODO:add some features") }

使用make创建切片:

go
package main import "fmt" type Rsvp struct{ Name,Email,Phone string WillAttend bool } var responses = make([]*Rsvp,0,10) func main() { fmt.Println("TODO:add some features") }

make用于创建数组,切片,字典

在上面使用make创建了一个初始化大小(size)为0,初始化容量(capacity)为10的切片

虽然初始化大小为0,但是当添加新元素的时候,切片会自动扩容,

创建一个HTML模板(party.html):

html
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Let's Party!</title> <link href= "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body class="p-2"> {{ block "body" . }} Content Goes Here {{ end }} </body> </html>

模板中,{{}}内容是用于插入动态数据

创建欢迎模板(welcome.html):

html
{{ define "body"}} <div class="text-center"> <h3> We're going to have an exciting party!</h3> <h4>And YOU are invited!</h4> <a class="btn btn-primary" href="/form"> RSVP Now </a> </div> {{ end }}

创建表格模板(form.html):

html
{{ define "body"}} <div class="h5 bg-primary text-white text-center m-2 p-2">RSVP</div> {{ if gt (len .Errors) 0}} <ul class="text-danger mt-3"> {{ range .Errors }} <li>{{ . }}</li> {{ end }} </ul> {{ end }} <form method="POST" class="m-2"> <div class="form-group my-1"> <label>Your name:</label> <input name="name" class="form-control" value="{{.Name}}" /> </div> <div class="form-group my-1"> <label>Your email:</label> <input name="email" class="form-control" value="{{.Email}}" /> </div> <div class="form-group my-1"> <label>Your phone number:</label> <input name="phone" class="form-control" value="{{.Phone}}" /> </div> <div class="form-group my-1"> <label>Will you attend?</label> <select name="willattend" class="form-select"> <option value="true" {{if .WillAttend}}selected{{end}}> Yes, I'll be there </option> <option value="false" {{if not .WillAttend}}selected{{end}}> No, I can't come </option> </select> </div> <button class="btn btn-primary mt-3" type="submit"> Submit RSVP </button> </form> {{ end }}

创建感谢页面(thanks.html:

html
{{ define "body"}} <div class="text-center"> <h1>Thank you, {{ . }}!</h1> <div> It's great that you're coming. The drinks are already in the fridge!</div> <div>Click <a href="/list">here</a> to see who else is coming.</div> </div> {{ end }}

创建遗憾界面(sorry.html):

html
{{ define "body"}} <div class="text-center"> <h1>It won't be the same without you, {{ . }}!</h1> <div>Sorry to hear that you can't make it, but thanks for letting us know.</div> <div> Click <a href="/list">here</a> to see who is coming, just in case you change your mind. </div> </div> {{ end }}

此界面用于对不能来参加的人标识遗憾

创建名单界面(list.html):

html
{{ define "body"}} <div class="text-center p-2"> <h2>Here is the list of people attending the party</h2> <table class="table table-bordered table-striped table-sm"> <thead> <tr><th>Name</th><th>Email</th><th>Phone</th></tr> </thead> <tbody> {{ range . }} {{ if .WillAttend }} <tr> <td>{{ .Name }}</td> <td>{{ .Email }}</td> <td>{{ .Phone }}</td> </tr> {{ end }} {{ end }} </tbody> </table> </div> {{ end }}

用于展示参加聚会的人的信息

加载模板:

go
package main import ( "fmt" "html/template" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) # 创建了一个以字符串为key,template.Template指针为值得字典 var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func main() { loadTemplates() }

创建http处理服务:

go
package main import ( "fmt" "html/template" "net/http" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func welcomeHandler(writer http.ResponseWriter, request *http.Request) { templates["welcome"].Execute(writer, nil) } func listHandler(writer http.ResponseWriter, request *http.Request) { templates["list"].Execute(writer, responses) } func main() { loadTemplates() http.HandleFunc("/", welcomeHandler) http.HandleFunc("/list", listHandler) }

func welcomeHandler(writer http.ResponseWriter, request *http.Request)

上面这个函数是用来处理http请求得函数,第二个参数是外部请求的指针

第一个参数是一个接口

http.HandleFunc("/", welcomeHandler):注册http请求处理函数

添加监听服务:

go
package main import ( "fmt" "html/template" "net/http" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func welcomeHandler(writer http.ResponseWriter, request *http.Request) { templates["welcome"].Execute(writer, nil) } func listHandler(writer http.ResponseWriter, request *http.Request) { templates["list"].Execute(writer, responses) } func main() { loadTemplates() http.HandleFunc("/", welcomeHandler) http.HandleFunc("/list", listHandler) err := http.ListenAndServe(":5000", nil) if err != nil { fmt.Println(err) } }

添加对表格的处理(get请求处理)

go
package main import ( "fmt" "html/template" "net/http" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func welcomeHandler(writer http.ResponseWriter, request *http.Request) { templates["welcome"].Execute(writer, nil) } func listHandler(writer http.ResponseWriter, request *http.Request) { templates["list"].Execute(writer, responses) } type foxData struct { *Rsvp Errors []string } func formHandler(writer http.ResponseWriter, request *http.Request) { if request.Method == http.MethodGet { templates["form"].Execute(writer, foxData{ Rsvp: &Rsvp{}, Errors: []string{}, }) } } func main() { loadTemplates() http.HandleFunc("/", welcomeHandler) http.HandleFunc("/list", listHandler) http.HandleFunc("/form", formHandler) err := http.ListenAndServe(":5000", nil) if err != nil { fmt.Println(err) } }

添加对表格的处理(post请求处理)

go
package main import ( "fmt" "html/template" "net/http" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func welcomeHandler(writer http.ResponseWriter, request *http.Request) { templates["welcome"].Execute(writer, nil) } func listHandler(writer http.ResponseWriter, request *http.Request) { templates["list"].Execute(writer, responses) } type foxData struct { *Rsvp Errors []string } func formHandler(writer http.ResponseWriter, request *http.Request) { if request.Method == http.MethodGet { templates["form"].Execute(writer, foxData{ Rsvp: &Rsvp{}, Errors: []string{}, }) } else if request.Method == http.MethodPost { request.ParseForm() responseData := Rsvp{ Name: request.Form["name"][0], Email: request.Form["email"][0], Phone: request.Form["phone"][0], WillAttend: request.Form["willattend"][0] == "true", } responses = append(responses, &responseData) if responseData.WillAttend { templates["thanks"].Execute(writer, responseData.Name) } else { templates["sorry"].Execute(writer, responseData.Name) } } } func main() { loadTemplates() http.HandleFunc("/", welcomeHandler) http.HandleFunc("/list", listHandler) http.HandleFunc("/form", formHandler) err := http.ListenAndServe(":5000", nil) if err != nil { fmt.Println(err) } }

添加数据验证

go
package main import ( "fmt" "html/template" "net/http" ) type Rsvp struct { Name, Email, Phone string WillAttend bool } var responses = make([]*Rsvp, 0, 10) var templates = make(map[string]*template.Template, 3) func loadTemplates() { templateNames := [5]string{"welcome", "form", "thanks", "sorry", "list"} for index, name := range templateNames { t, err := template.ParseFiles("party.html", name+".html") if err == nil { templates[name] = t fmt.Println("Loaded template", index, name) } else { panic(err) } } } func welcomeHandler(writer http.ResponseWriter, request *http.Request) { templates["welcome"].Execute(writer, nil) } func listHandler(writer http.ResponseWriter, request *http.Request) { templates["list"].Execute(writer, responses) } type foxData struct { *Rsvp Errors []string } func formHandler(writer http.ResponseWriter, request *http.Request) { if request.Method == http.MethodGet { templates["form"].Execute(writer, foxData{ Rsvp: &Rsvp{}, Errors: []string{}, }) } else if request.Method == http.MethodPost { request.ParseForm() responseData := Rsvp{ Name: request.Form["name"][0], Email: request.Form["email"][0], Phone: request.Form["phone"][0], WillAttend: request.Form["willattend"][0] == "true", } errors := []string{} if responseData.Name == "" { errors = append(errors, "please enter your name!") } if responseData.Email == "" { errors = append(errors, "please enter your email!") } if responseData.Phone == "" { errors = append(errors, "please enter your phone number!") } if len(errors) > 0 { templates["form"].Execute(writer, foxData{ Rsvp: &responseData, Errors: errors, }) } else { responses = append(responses, &responseData) if responseData.WillAttend { templates["thanks"].Execute(writer, responseData.Name) } else { templates["sorry"].Execute(writer, responseData.Name) } } } } func main() { loadTemplates() http.HandleFunc("/", welcomeHandler) http.HandleFunc("/list", listHandler) http.HandleFunc("/form", formHandler) err := http.ListenAndServe(":5000", nil) if err != nil { fmt.Println(err) } }

go工具

go命令行

  • build:编译源代码并生成可执行文件
  • clean:删除所有build命令生成的文件,包括可执行文件和临时文件
  • doc:根据源代码生成文档
  • fmt:标准格式化源代码
  • get:下载并安装其他的包
  • install:下载并安装,常用于安装工具包
  • help:查看帮助,比如查看build的帮助:go help build
  • mod:包管理命令
  • run:编译并运行go代码,注意,这个会将生成的二进制文件放在临时目录中,不如build放在命令执行目录中
  • test:单元测试命令
  • version:查看go版本
  • vet:

基础数据类型

go
package main import ( "fmt" "math/rand" ) func main() { fmt.Println(rand.Int()) }

使用import导入的时候,有两个关键:

  1. 使用import关键字
  2. 包的路径,可以是本地路径,也可以是github路径

基础数据类型:

  • int:整型,可正可负,根据系统的不同,分为32位和64位;一般来说需要用到大数的时候最好指定32或者64(int32 or int64)
  • uint:非负整数,同样根据系统不同分为32位和64位,也有uint32和uint64等详细区分
  • byte:其实就是uint8,代表8位二进制数,也就是一个字节的数据
  • float32和float64:分别代表32位和64位的浮点数
  • complex64,complex128:分别代表32位和64位的实数+虚数
  • bool
  • string
  • rune:代表一个单独的unicode字符,比如:汉字"你"就是一个unicode字符,但是可能汉字在ascii码中是三个或者四个字符

常量:可重复使用并且不可更改

常量分为两种:指定类型和非指定类型

指定类型就是在定义时指定了常量的类型:

go
package main import ( "fmt" //"math/rand" ) func main() { const price float32 = 275.00 const tax float32 = 27.50 const quantity int = 2 fmt.Println("Total:", quantity * (price + tax)) }

上面就指定了quantity的常量类型为int,由于go中没有自动类型转换,当int和float32混合计算的时候,类型不一致,不能混合计算将导致报错:

invalid operation: quantity * (price + tax) (mismatched types int and float32)

这个时候使用非指定类型就方便一些了:

go
package main import ( "fmt" //"math/rand" ) func main() { const price float32 = 275.00 const tax float32 = 27.50 const quantity = 2 fmt.Println("Total:", quantity * (price + tax)) }

上面两个例子的区别就是没有指定quantity的类型,由编译器自动推断,所以第二个例子就没有报错。

iota自增常量

go
package main import ( "fmt" ) const ( one = iota + 1 two three four five ) func main() { fmt.Println("one:", one) fmt.Println("two:", two) fmt.Println("three:", three) fmt.Println("four:", four) fmt.Println("five:", five) }

同时定义多个常量:

go
const price,tax,quantity float32 = 275.00,27.50,2.33 const price,insock = 2,true

变量

变量和常量的区别就是变量可以修改,而常量不行:

go
package main import "fmt" func main() { var price float32 = 275.00 var tax float32 = 27.50 fmt.Println(price + tax) price = 300 fmt.Println(price + tax) }

同理,变量在定义的时候也可以省略变量类型,起到非指定类型的效果

go
package main import "fmt" func main() { var price = 275.00 var price2 = price fmt.Println(price) fmt.Println(price2) }

注意:go内部默认将浮点值转换为float64位,所以如果将非指定类型的浮点值与float32进行计算,会报错

定义变量但不初始化赋值

go
package main import "fmt" func main() { var price float32 fmt.Println(price) price = 275.00 fmt.Println(price) } D:\goProject\golearn\lession2>go run basic.go 0 275

可以看出,虽然没有初始化赋值,但是自动赋值为类型0值

在一条语句中定义多个变量:

go
package main import "fmt" func main() { var price, tax = 275.00, 27.50 fmt.Println(price + tax) } D:\goProject\golearn\lession2>go run basic.go 302.5

重定义变量

变量一般是不允许再次声明定义的,所以下面的会报错

go
package main import "fmt" func main() { price, tax, inStock := 275.00, 27.50, true fmt.Println("Total:", price+tax) fmt.Println("In stock:", inStock) var price2, tax = 200.00, 25.00 fmt.Println("Total 2:", price2+tax) }

因为tax再之前已经使用短变量声明定义了

但是如果后面的不是使用var声明,而是也是使用短变量声明以及同时还有另一个未声明的变量,那么就可以重新定义,但是注意,变量类型不能变

go
package main import "fmt" func main() { price, tax, inStock := 275.00, 27.50, true fmt.Println("Total:", price+tax) fmt.Println("In stock:", inStock) price2, tax := 200.00, 25.00 fmt.Println("Total 2:", price2+tax) }

可以看见,和之前的差别就是使用短变量声明法以及同时还有一个未声明变量同时赋值

指针

示例:

go
var first int = 100 var second *int = &first

*int表示这个指针是整型指针,*表示这是一个指针,&是取地址符,&first表示结果是first的内存地址;上面总结就是定义一个整型指针,指针内容是first的地址

打印的区别:

go
fmt.Println("second 内容",second) #输出first的地址(类似于0xc000010088); fmt.Println("second 内容代表的值",*second) #输出first的值(100

指针的计算:

shell
*second++ 是可以的,等同于fitst++ var third *int = second 可以将指针的值赋值给另一个指针

指针的零值

go
package main import "fmt" func main() { first := 100 var second *int fmt.Println(second) second = &first fmt.Println(second) } D:\goProject\golearn\lession2>go run basic.go <nil> 0xc000012098

指针的零值是nil,并且如果在此时去读取指针地址的内容,那么将会报错:

go
package main import "fmt" func main() { first := 100 var second *int fmt.Println(*second) # 读取nil指针的值内容 second = &first fmt.Println(second) } D:\goProject\golearn\lession2>go run basic.go panic: runtime error: invalid memory address or nil pointer dereference [signal 0xc0000005 code=0x0 addr=0x0 pc=0xbcc593] goroutine 1 [running]: main.main() D:/goProject/golearn/lession2/basic.go:8 +0x33 exit status 2

指针的指针也是可以允许的:

go
package main import "fmt" func main() { first := 100 second := &first third := &second fmt.Println(first) fmt.Println(*second) fmt.Println(**third) } D:\goProject\golearn\lession2>go run basic.go 100 100 100

那么在读取的时候就需要两个读取符来读取地址内容了。

操作和转换

基础的计算,比较和逻辑运算是到处都会用到的。

由于go的严格类型规则,不允许不通类型的值进行同一个运算。

基本操作的操作符类似其它语言的操作符,但是类型转换可以使用内置函数或者第三方库。

基础计算符:

go
计算操作:+ - * / %:加减乘除和取余 比较操作:"==" "!=" < <= > >= :对于两个数的大小比较 逻辑操作:|| && !:或并非;逻辑计算 赋值操作:= =: 前者是赋予变量值,后者是声明和赋值一起 增减操作:-= += ++ -- 位操作:& | ^ &^ << >>

计算操作

计算操作符除了取余操作符只能用于整型,其它的可以用于所有数值类型(float32 float64 int int32 int64...)。

计算操作只能用于同一个类型或者能够被同一个类型表示的两个值。

数值溢出

golang允许数值溢出,这在其它一些语言中会报错。

示例:

go
package main import ( "fmt" "math" ) func main() { var intVal = math.MaxInt64 var floatVal = math.MaxFloat64 fmt.Println(intVal * 2) fmt.Println(floatVal * 2) fmt.Println(math.IsInf((floatVal * 2), 0)) } D:\goProject\golearn\lession2>go run basic.go -2 +Inf # 溢出到正无穷大 true

取余操作

go中的取余操作和python中的取模操作类似,但是go的取余操作可以对负整数使用。

go
package main import ( "fmt" "math" ) func main() { posResult := 3 % 2 negResult := -3 % 2 absResult := math.Abs(float64(negResult)) fmt.Println(posResult) fmt.Println(negResult) fmt.Println(absResult) } D:\goProject\golearn\lession2>go run basic.go 1 -1 1

string操作

string拼接

可以使用操作符"+"将两个string值拼接在一起:

go
package main import ( "fmt" // "math" ) func main() { greeting := "Hello" language := "Go" combinedString := greeting + ", " + language fmt.Println(combinedString) } D:\goProject\golearn\lession2>go run . Hello, Go

比较运算

比较运算必须在两个相同的数据类型之间比较,或者都是非指定类型变量或常量,并且可以看作更大类型。

示例:

go
package main import ( "fmt" // "math" ) func main() { first := 100 const second = 200.00 equal := first == second notEqual := first != second lessThan := first < second lessThanOrEqual := first <= second greaterThan := first > second greaterThanOrEqual := first >= second fmt.Println(equal) fmt.Println(notEqual) fmt.Println(lessThan) fmt.Println(lessThanOrEqual) fmt.Println(greaterThan) fmt.Println(greaterThanOrEqual) } D:\goProject\golearn\lession2>go run . false true true true false false

上面的示例中,因为second是非指定类型常量,并且小数点后为0,可以看作更大类型的整型,而且first也可以看作更大类型的整型,所以可以放在一起比较,如果second是200.01,那就会报错:

go
D:\goProject\golearn\lession2>go run . # lession2 .\basic.go:11:17: constant 200.01 truncated to integer .\basic.go:12:20: constant 200.01 truncated to integer .\basic.go:13:20: constant 200.01 truncated to integer .\basic.go:14:27: constant 200.01 truncated to integer .\basic.go:15:23: constant 200.01 truncated to integer .\basic.go:16:30: constant 200.01 truncated to integer

指针的比较运算

通过比较运算可以判断两个指针是否指向同一个地址。

go
package main import ( "fmt" // "math" ) func main() { first := 100 second := &first third := &first alpha := 100 beta := &alpha fmt.Println(second == third) fmt.Println(second == beta) } D:\goProject\golearn\lession2>go run . true false

逻辑运算

|| 或,A || B,如果A计算结果是真,那么B就不计算了;A和B至少一个是真,结果为真,都为假,结果为假

&& 与,A && B,A和B同时为真,结果为真,如果其中一个为假,则结果为假;如果A计算结果为假,那么结果为假,B不计算。

! 非,取反,!A,若A计算结果为真,则整个计算结果取反为假,若A计算结果为假,取反,则整个计算结果为真。

示例:

go
package main import ( "fmt" // "math" ) func main() { maxMph := 50 passengerCapacity := 4 airbags := true familyCar := passengerCapacity > 2 && airbags sportsCar := maxMph > 100 || passengerCapacity == 2 canCategorize := !familyCar && !sportsCar fmt.Println(familyCar) fmt.Println(sportsCar) fmt.Println(canCategorize) } D:\goProject\golearn\lession2>go run . true false false

数值转换解析和格式化

go不允许不同的数值类型在计算中混用,也不会自动转换数值类型,除非是未定义类型变量。如果混用就会报错,错误的示例如下:

go
package main import ( "fmt" // "math" ) func main() { kayak := 275 soccerBall := 19.50 total := kayak + soccerBall fmt.Println(total) } D:\goProject\golearn\lession2>go run . # lession2 .\basic.go:11:17: invalid operation: kayak + soccerBall (mismatched types int and float64)

如上所示,错误提示是int和float64混用。

显式类型转换

可以通过显示类型转换手动将数值转换为想要计算的数值类型,示例如下:

go
package main import ( "fmt" // "math" ) func main() { kayak := 275 soccerBall := 19.50 total := float64(kayak) + soccerBall fmt.Println(total) } D:\goProject\golearn\lession2>go run . 294.5

上面通过使用float64(kayak)转换成float64类型。

显示转换的表达式是T(x),T是目标类型,x当前的变量名或值

显式转换的局限性

显式转换只能在当前值能够被目标类型表达的时候进行,比如两个数值类型可以显式转化,字符串可以和runes之间可以转换;但是想把整型转换成布尔型就不行了。

显示转换可能会丢失精确度或者导致数值溢出,要注意。

示例:

go
package main import ( "fmt" // "math" ) func main() { kayak := 275 soccerBall := 19.50 total := kayak + int(soccerBall) fmt.Println(total) fmt.Println(int8(total)) } D:\goProject\golearn\lession2>go run . 294 38

如上所示,19.50转换成整型变成了19,而int类型转换为int8类型就数值溢出了。

将浮点值转换为整型

将浮点值转换为整型是有风险的,不过使用math包里面的转换函数就比较安全。

  • Ceil(value) :返回大于浮点值的最小整数
  • Floor(value):返回小于浮点值的最大整数
  • Round(value):返回最接近浮点值的整数
  • RoundToEven(value):返回最接近浮点值的偶数

注意,上述函数的参数都是float64类型,返回的都是float64类型,只是返回的小数部分是0(比如:math.Round(19.32)返回19.00),所以仍需要进行显式转换为int类型才能与int类型进行计算

go
package main import ( "fmt" "math" ) func main() { kayak := 275 soccerBall := 19.50 total := kayak + int(math.Round(soccerBall)) fmt.Println(total) } D:\goProject\golearn\lession2>go run . 295

字符串转换数值类型

go的标准库中有strconv包用来将string类型转换为其它类型。

包中的函数如下所示:

  • ParseBool(str):将字符串转为布尔型,只有当字符串类似后面的内容时才行:true True TRUE T 0 或者false False FALSE F 1
  • ParseFloat(str,size):将字符串转为指定size的浮点型
  • ParseInt(str,base,size):将字符串转为指定类型的int64,base代表字符串内容的进制,0,2,8,10,16进制;0进制代表根据字符串自动判断
  • ParseUint(str,base,size):类似上面,只是转换的是无符号整数
  • Atoi(str):将字符串转为10进制整数类型,等同于ParseInt(str,10,0)

转换字符串为布尔类型示例:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "true" val2 := "false" val3 := "not true" bool1, b1err := strconv.ParseBool(val1) bool2, b2err := strconv.ParseBool(val2) bool3, b3err := strconv.ParseBool(val3) fmt.Println("Bool 1", bool1, b1err) fmt.Println("Bool 2", bool2, b2err) fmt.Println("Bool 3", bool3, b3err) } D:\goProject\golearn\lession2>go run . Bool 1 true <nil> Bool 2 false <nil> Bool 3 false strconv.ParseBool: parsing "not true": invalid syntax

字符串转为整型

字符串转为整型使用的是strconv.ParseInt(str,base,size);其中str是字符串,base是进制,size是多少位能容纳下转换后的数值,但是注意,这个函数返回的类型是int64,比如500,8位二进制最大时256,所以strconv.Parse("500",0,8)就会报错,因为8位容纳不下500这个值。示例如下:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "100" int1, int1err := strconv.ParseInt(val1, 0, 8) if int1err == nil { fmt.Println("Parsed value:", int1) } else { fmt.Println("Cannot parse", val1) } } D:\goProject\golearn\lession2>go run . Parsed value: 100 # 将字符串内容改为"500":D:\goProject\golearn\lession2>go run . package main import ( "fmt" "strconv" ) func main() { val1 := "500" int1, int1err := strconv.ParseInt(val1, 0, 8) if int1err == nil { fmt.Println("Parsed value:", int1) } else { fmt.Println("Cannot parse", val1, int1err) } } D:\goProject\golearn\lession2>go run . Cannot parse 500 strconv.ParseInt: parsing "500": value out of range

由于strconv.ParseInt返回的是Int64类型,但是我们不需要这么大的类型,只需要比如int8类型,那么当size设置为8的时候,如果能够转换成功,那么再使用int8(result)来再次转换是非常安全的。

二进制,八进制和十六进制字符串转为整数

下面的示例,将100视为二进制的数用来转换,转换为整数的结果是4:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "100" int1, int1err := strconv.ParseInt(val1, 2, 8) if int1err == nil { smallInt := int8(int1) fmt.Println("Parsed value 10:", smallInt) } else { fmt.Println("Cannot parse", val1, int1err) } } D:\goProject\golearn\lession2>go run . Parsed value 10: 4

使用前缀判断字符串内容是什么进制:

  • 0b:二进制
  • 0o:八进制
  • 0x:十六进制

示例:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "0b1100100" int1, int1err := strconv.ParseInt(val1, 0, 8) if int1err == nil { smallInt := int8(int1) fmt.Println("Parsed value:", smallInt) } else { fmt.Println("Cannot parse", val1, int1err) } } D:\goProject\golearn\lession2>go run . Parsed value: 100

转换为十进制数有两种办法,一种是使用ParseInt(str,10,size),一种是Atoi(str),如下:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "100" int1, int1err := strconv.ParseInt(val1, 10, 0) if int1err == nil { var intResult int = int(int1) fmt.Println("Parsed value:", intResult) } else { fmt.Println("Cannot parse", val1, int1err) } } package main import ( "fmt" "strconv" ) func main() { val1 := "100" int1, int1err := strconv.Atoi(val1) if int1err == nil { var intResult int = int1 fmt.Println("Parsed value:", intResult) } else { fmt.Println("Cannot parse", val1, int1err) } } 其实可以把Atoi看错strconv.ParseInt(str,10,0)

字符串转换为浮点型

主要是使用ParseFloat(str,size)函数,其中str是待转换的字符串,size是指定转换后的位数,但是注意,和ParseInt类似,ParseFLoat返回的是float64类型,如果将size指定为32,那么显式转换为float32的时候,不会有精度损失。

go
package main import ( "fmt" "strconv" ) func main() { val1 := "48.95" float1, float1err := strconv.ParseFloat(val1, 64) if float1err == nil { fmt.Println("Parsed value:", float1) } else { fmt.Println("Cannot parse", val1, float1err) } } D:\goProject\golearn\lession2>go run . Parsed value: 48.95

将科学计数法的字符串转为浮点值:

go
package main import ( "fmt" "strconv" ) func main() { val1 := "4.895e+01" float1, float1err := strconv.ParseFloat(val1, 64) if float1err == nil { fmt.Println("Parsed value:", float1) } else { fmt.Println("Cannot parse", val1, float1err) } } D:\goProject\golearn\lession2>go run . Parsed value: 48.95

将数值转为字符串

将数值转为字符串主要使用以下几个函数:

  • FormatBool(val):返回字符串的true或者false
  • FormatInt(val,base)
    是val的进制,根据val返回数字的int64字符串,
  • FormatUint(val,base):
  • FormatFloat(val,format,precision,size):根据指定的格式,精度和大小返回float64的浮点数
  • Itoa(val):直接转为整数int64

转换布尔值

FormatBool接收一个布尔值,返回其代表值的字符串:

go
package main import ( "fmt" "strconv" ) func main() { val1 := true val2 := false str1 := strconv.FormatBool(val1) str2 := strconv.FormatBool(val2) fmt.Println("Formatted value 1: " + str1) fmt.Println("Formatted value 2: " + str2) } D:\goProject\golearn\lession2>go run . Formatted value 1: true Formatted value 2: false

转换整数

FormatInt接收两个参数,一个是int64类型的整数,一个是这个整数转换后的进制;

示例:

go
package main import ( "fmt" "strconv" ) func main() { val := 275 base10String := strconv.FormatInt(int64(val), 10) base2String := strconv.FormatInt(int64(val), 2) fmt.Println("Base 10: " + base10String) fmt.Println("Base 2: " + base2String) } D:\goProject\golearn\lession2>go run . Base 10: 275 Base 2: 100010011

也可以使用Itoa函数来转化,Itoa函数可视为:ForamtInt(int64(var),10),示例:

go
package main import ( "fmt" "strconv" ) func main() { val := 275 base10String := strconv.Itoa(val) base2String := strconv.FormatInt(int64(val), 2) fmt.Println("Base 10: " + base10String) fmt.Println("Base 2: " + base2String) } D:\goProject\golearn\lession2>go run . Base 10: 275 Base 2: 100010011

转换浮点数

浮点数需要指定更多的参数和不同的格式,示例:

go
package main import ( "fmt" "strconv" ) func main() { val := 49.95 Fstring := strconv.FormatFloat(val, 'f', 2, 64) Estring := strconv.FormatFloat(val, 'e', -1, 64) fmt.Println("Format F: " + Fstring) fmt.Println("Format E: " + Estring) } D:\goProject\golearn\lession2>go run . Format F: 49.95 Format E: 4.995e+01

FormatFloat函数有4个参数:

  1. 待转换的浮点数
  2. 这是一个byte值,指定字符串的格式,'f'代表(+|-)ddd.ddd格式,比如56.32;'e'|'E'代表(+|-)ddd.ddde(+-)dd格式,比如34.11e3(等于34110);'g'|'G'代表的数值很大的时候用'e'|'E',小的时候用'f'
  3. 精度控制参数,即小数点后的位数,-1表示当转换成的字符串再次转换为浮点数的时候,不损失精度的情况下,最小的小数点位数
  4. size,32或64,指定浮点数的大小,和前面的类似

流程控制

分类:

  • 条件判断,if-else
  • 循环,for
  • 打断循环,break和continue
  • 枚举,for
  • 条件的复杂比较,switch
  • 强制一个case语句跳入下一个case语句,fallthrough
  • 跳转,label

if和else条件判断

go
package main import "fmt" func main() { kayakPrice := 275.00 if kayakPrice > 100 { fmt.Println("Price is greater than 100") } } D:\goProject\golearn\lession2>go run . Price is greater than 100

使用格式:

go
if 条件判断 { 执行语句 } 条件判断必须在同一行中

else

go
package main import "fmt" func main() { kayakPrice := 275.00 if kayakPrice < 200 { fmt.Println("Price is lower than 200") } else if kayakPrice > 100 { fmt.Println("Price is bigger than 100") } } D:\goProject\golearn\lession2>go run . Price is bigger than 100 package main import "fmt" func main() { kayakPrice := 275.00 if kayakPrice < 200 { fmt.Println("Price is lower than 200") } else { fmt.Println("Price is bigger than 200") } } D:\goProject\golearn\lession2>go run . Price is bigger than 200

作用域

if的作用域就在if的两个大括号之间,所以如果在这之间定义了变量,那么在语句结束之后,变量也就失效,所以可以在if和else if中重复定义变量

shell
package main import "fmt" func main() { kayakPrice := 275.00 if kayakPrice > 500 { scopedVar := 500 fmt.Println("Price is greater than", scopedVar) } else if kayakPrice < 100 { scopedVar := "Price is less than 100" fmt.Println(scopedVar) } else { scopedVar := false fmt.Println("Matched: ", scopedVar) } } D:\goProject\golearn\lession2>go run . Matched: false 上面在if和else if和else中都定义了scopedVar变量,并没有报错

在if的条件语句中使用初始化语句

go
package main import ( "fmt" "strconv" ) func main() { priceString := "275" if kayakPrice, err := strconv.Atoi(priceString); err == nil { fmt.Println("Price:", kayakPrice) } else { fmt.Println("Error:", err) } fmt.Println(kayakPrice) } D:\goProject\golearn\lession2>go run . # lession2 .\basic.go:15:14: undefined: kayakPrice 注意,在if条件语句中定义并初始化的变量,在if外是不能使用的 但是如果是在if外定义的,在if条件语句中初始化,那么就可以在if外使用

for循环

go中只有一个循环就是for循环,没有while这些循环

for循环格式:

go
for 初始化语句;条件语句;增长语句 { 执行语句 } 示例: for i:=1;i<10;i++{ fmt.Println("i=",i) } package main import ( "fmt" //"strconv" ) func main() { for i := 1; i < 10; i++ { fmt.Println("i=", i) } } D:\goProject\golearn\lession2>go run . i= 1 i= 2 i= 3 i= 4 i= 5 i= 6 i= 7 i= 8 i= 9for3个语句就是空的时候,就是无限循环,需要使用break才能退出循环 3个语句也不一定全部都有,可以只有一部分,如下: package main import ( "fmt" //"strconv" ) func main() { counter := 0 for counter <= 3 { fmt.Println("Counter:", counter) counter++ // if (counter > 3) { // break // } } } D:\goProject\golearn\lession2>go run . Counter: 0 Counter: 1 Counter: 2 Counter: 3

break和continue

这两个和其它语言的作用是一样的

for枚举

枚举列表

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { fmt.Println("Index:", index, "Character:", string(character)) } } D:\goProject\golearn\lession2>go run . Index: 0 Character: K Index: 1 Character: a Index: 2 Character: y Index: 3 Character: a Index: 4 Character: k 如果取消输出中的显式string转换: D:\goProject\golearn\lession2>go run . Index: 0 Character: 75 Index: 1 Character: 97 Index: 2 Character: 121 Index: 3 Character: 97 Index: 4 Character: 107 这是因为for将字符串视为一个个rune组成的列表

rune和byte的区别

rune的底层是int32类型,byte底层是uint8类型,在处理英文字符的时候,两者差别不大,但是在处理中文以及特殊字符的时候就有区别了。

byte代表一个ascii字符,rune代表一个utf-8字符。

示例:

go
package main import ( "fmt" //"strconv" ) func main() { str := "hello 世界" fmt.Println(len(str)) str2 := []rune(str) fmt.Println(len(str2)) for i := 0; i < len(str); i++ { fmt.Print(string(str[i])) } fmt.Println("\n----------") for _, v := range str { fmt.Print(string(v)) } } D:\goProject\golearn\lession2>go run . 12 8 hello ä¸ç ---------- hello 世界 可以看见使用byte来计算长度的时候,计算的是底层字节长度,在计算ascii码的时候还可以,但是涉及中文就不行了 使用rune来计算的时候,计算的是utf-8编码的字节长度,可以很方便的计算中文等特殊字符的长度。 遍历的时候,使用for..range更好,因为for..range会隐式的unicode解码

switch

switch提供另一种方式来控制流程,基于匹配表达式结果和一个特定的值:

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { switch character { case 'K': fmt.Println("K at position", index) case 'y': fmt.Println("y at position", index) } } } D:\goProject\golearn\lession2>go run . K at position 0 y at position 2

switch可以匹配多个值:

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { switch character { case 'K', 'k': fmt.Println("K or k at position", index) case 'y': fmt.Println("y at position", index) } } } D:\goProject\golearn\lession2>go run . K or k at position 0 y at position 2 K or k at position 4

中止switch的执行:

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { switch character { case 'K', 'k': if character == 'k' { fmt.Println("Lowercase k at position", index) break } fmt.Println("Uppercase K at position", index) case 'y': fmt.Println("y at position", index) } } } D:\goProject\golearn\lession2>go run . Uppercase K at position 0 y at position 2 Lowercase k at position 4 注意:break在打印了小写的k之后中止,退出了switch循环,但是没有退出for循环,所以让然会对下一个字符进行判断

fallthrough

fallthrough是可以强制执行下一个case中的语句:

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { switch character { case 'K': fmt.Println("Uppercase character") fallthrough case 'p': fmt.Println("not match but fall through") case 'y': fmt.Println("y at position", index) } } } D:\goProject\golearn\lession2>go run . Uppercase character not match but fall through y at position 2

default

当所有case都没有匹配的时候,就可以使用default来接收:

go
package main import ( "fmt" //"strconv" ) func main() { product := "Kayak" for index, character := range product { switch character { case 'K', 'k': if character == 'k' { fmt.Println("Lowercase k at position", index) break } fmt.Println("Uppercase K at position", index) case 'y': fmt.Println("y at position", index) default: fmt.Println("Character", string(character), "at position", index) } } } D:\goProject\golearn\lession2>go run . Uppercase K at position 0 Character a at position 1 y at position 2 Character a at position 3 Lowercase k at position 4

在switch中进行初始化

可以在switch的条件语句中添加初始化语句,用于每次循环:

go
package main import ( "fmt" //"strconv" ) func main() { for counter := 0; counter < 20; counter++ { switch val := counter / 2; val { case 2, 3, 5, 7: fmt.Println("Prime value:", val) default: fmt.Println("Non-prime value:", val) } } } D:\goProject\golearn\lession2>go run . Non-prime value: 0 Non-prime value: 0 Non-prime value: 1 Non-prime value: 1 Prime value: 2 Prime value: 2 Prime value: 3 Prime value: 3 Non-prime value: 4 Non-prime value: 4 Prime value: 5 Prime value: 5 Non-prime value: 6 Non-prime value: 6 Prime value: 7 Prime value: 7 Non-prime value: 8 Non-prime value: 8 Non-prime value: 9 Non-prime value: 9

省略switch的比较值

可以将比较的语句放在case中,达到更多更灵活的使用:

go
package main import ( "fmt" //"strconv" ) func main() { for counter := 0; counter < 10; counter++ { switch { case counter == 0: fmt.Println("Zero value") case counter < 3: fmt.Println(counter, "is < 3") case counter >= 3 && counter < 7: fmt.Println(counter, "is >= 3 && < 7") default: fmt.Println(counter, "is >= 7") } } } Zero value 1 is < 3 2 is < 3 3 is >= 3 && < 7 4 is >= 3 && < 7 5 is >= 3 && < 7 6 is >= 3 && < 7 7 is >= 7 8 is >= 7 9 is >= 7

其实就是case语句需要一个布尔类型的结果,如果switch中有值,那么就和case的值进行等于比较,返回case一个布尔值;如果switch中没有比较值,那么就计算case语句中表达式的布尔值,返回给case

标签(尽量不用这玩意儿)

go
package main import ( "fmt" //"strconv" ) func main() { counter := 0 target: fmt.Println("Counter", counter) counter++ if counter < 5 { goto target } } D:\goProject\golearn\lession2>go run . Counter 0 Counter 1 Counter 2 Counter 3 Counter 4

数组,切片和maps

数组存储固定长度的值,切片存储可变长度的值,maps存储key-value键值对

数组

数组是一个固定长度并且包含单一类型元素的容器,可以通过下标访问元素。是值传递的。

切片

创建:

shell
make([]string,6,10) 创建一个字符串切片,长度为6,容量为10 或者从现有的数组创建: var arr [3]string={"123","133","5234"} sli := arr[:]

切片底层是数组,且注意,切片的第一个元素并不一定是底层数组的第一个元素。

切片有三个元素:底层数组,长度,容量;长度是切片包含的元素个数,容量是切片第一个元素到底层数组最后一个元素的个数。

为什么有容量?比如两个切片底层是一个数组,当两个切片的长度都小于容量的时候,修改A切片的内容可能会影响到B切片的数据,当切片的长度大于容量的时候,会重新分配底层数组,这时两个切片就不相关了。

一般使用for...range..来遍历切片

使用sort包的sort函数可以为切片排序

切片只能和nil比较

copy

copy函数保证每个切片有自己独立的数组。

copy(slice dest,slice src)

注意

不会自动扩容,如果在copy的过程中,目的切片不够了,那么就会中止copy,导致数据丢失!

如果目的切片没有初始化,也没有赋予初始长度容量,那么当其作为copy的目的切片时,不会赋值元素进去,且不会报错。

如果源切片长于目的切片,那么目的切片的前部分会被源切片元素覆盖,后面的保持不变。

将切片转为数组

shell
func main() { p1 := []string { "Kayak", "Lifejacket", "Paddle", "Hat"} arrayPtr := (*[3]string)(p1) array := *arrayPtr fmt.Println(array) }

字符串也可以当作切片取值,但是注意,取单个元素是byte,多个元素是string

shell
var price string = "$48.95" var currency string = string(price[0]) var amountString string = price[1:] amount, parseErr := strconv.ParseFloat(amountString, 64) fmt.Println("Currency:", currency) if parseErr == nil { fmt.Println("Amount:", amount) } else { fmt.Println("Parse Error:", parseErr) }

但是注意,上面的例子中金钱符号占据一个byte,但是如果有个符号占据3个byte的话,整个代码就出错了,所以需要将string转为rune数组来操作就可以了:

shell
var price []rune = []rune("€48.95") var currency string = string(price[0]) var amountString string = string(price[1:]) amount, parseErr := strconv.ParseFloat(amountString, 64) fmt.Println("Length:", len(price)) fmt.Println("Currency:", currency) if parseErr == nil { fmt.Println("Amount:", amount) } else { fmt.Println("Parse Error:", parseErr) }

所以关注存储空间长度使用byte,关注内容使用rune

使用for循环string的时候,使用rune来做循环的。

map

map是键值对类型,每个元素都有其单独且唯一的key对应。

创建map:

shell
make(map[string]float64,10) 创建一个以字符串为key,float64为value的map,初始长度为10 map_test:=map[string]int64{ "1":1, "2":2, }

判断key是否在map中

当key没在map中,取这个key的value会返回零值,这容易与存值混淆,解决方法:

shell
# 不要使用零值判断是否在map中 value,ok:=map_test["3"]

删除key:

shell
delete(map_test,"2")

遍历map:

shell
for key,value range map_test:

使用key排序,只有先取出所有key,然后对key排序,再使用key取值:

shell
products := map[string]float64 { "Kayak" : 279, "Lifejacket": 48.95, "Hat": 0, } keys := make([]string, 0, len(products)) for key, _ := range products { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { fmt.Println("Key:", key, "Value:", products[key]) }

函数

可变长度参数:

shell
func printSuppliers(product string, suppliers ...string) { for _, supplier := range suppliers { fmt.Println("Product:", product, "Supplier:", supplier) } }

可变长度参数必须是最后一个参数。

当可变参数没有值的时候是nil,所有有必要对其进行判断:

shell
func printSuppliers(product string, suppliers ...string) { if len(suppliers) == 0 { fmt.Println("Product:", product, "Supplier: (none)") } else { for _, supplier := range suppliers { fmt.Println("Product:", product, "Supplier:", supplier) } } }

可以使用切片作为可变参数的值。

文件操作

go读写参数:

image-20240527170235444

golang对文件的操作总体来说是增删改

打开文件

有两种方式:

  • Open(filename),返回一个文件的指针,且为只读
  • OpenFile(filename,flag,perm),返回一个文件指针,对文件的读写方式是有flag来执行,文件权限有perm指定
go
[root@localhost golearn]# cat main.go package main import "os" func main(){ file,_ := os.OpenFile("file.txt",os.O_APPEND|os.O_CREATE|os.O_WRONLY,0644) file.Write([]byte("append data\n")) file.Close() }

创建目录文件

go
os.MkdirAll(dir,perm),创建一个目录,MkdirALL类似于mkdir -p os.Create(filepath),创建一个文件,但是其目录必须存在 os.Remove(filepath),删除一个文件 os.RemoveAll(dir),删除一个目录

获取文件或目录的信息

shell
使用os.Stat()来获取文件信息,此函数返回os.FileInfo结构,此结构包含文件必要信息

示例:

shell
package main import ( "fmt" "os" ) func main() { file,_ := os.Stat("main.go") fp := fmt.Println fp("filename:",file.Name()) fp("size by bytes:",file.Size()) fp("last modified time:",file.ModTime()) fp("is a dir:",file.IsDir()) ff := fmt.Printf ff("Permission 9 bit:%s\n",file.Mode()) ff("Permission 3 bit:%o\n",file.Mode()) ff("Permission 4 bit:%04o\n",file.Mode()) } [root@localhost golearn]# go run main.go filename: main.go size by bytes: 385 last modified time: 2024-05-30 17:11:59.914381643 +0800 CST is a dir: false Permission 9 bit:-rw-r--r-- Permission 3 bit:644 Permission 4 bit:0644

查看一个目录中的文件或文件夹信息

shell
package main import ( "fmt" "log" "os" ) func main() { files, err := os.ReadDir("/tmp") for _, file := range files { fmt.Println("file name:", file.Name()) fmt.Println("is dir:", file.IsDir()) fmt.Println("################") } if err != nil { log.Fatal("Encounter Error:", err) } } [root@localhost golearn]# go run main.go file name: .ICE-unix is dir: true ################ file name: .Test-unix is dir: true ################ file name: .X1024-lock is dir: false ################ file name: .X11-unix is dir: true ################ file name: .XIM-unix is dir: true ################ ....

文件读写

当读写文件时,有时会使用缓冲buff来优化读写过程。

bufio的Scanner接口是

纯文本文件

读取:

go
package main import ( "bufio" "fmt" "os" ) func main() { file, _ := os.Open("./file.txt") # 前面说过噻,返回一个只读的文件对象 defer file.Close() scanner := bufio.NewScanner(file) # 使用文件对象创建一个缓存读取器 scanner.Split(bufio.ScanLines) # Split使用指定的分割函数处理文本,默认就是使用bufio.ScanLines函数,用于按照行分割,就是说读取到函数返回true的时候就截断 var lines []string for scanner.Scan() { lines = append(lines, scanner.Text()) } for _, line := range lines { fmt.Println(line) } }

写入:

go
package main import ( "bufio" "os" ) func main() { lines := []string{"3333", "4444", "5555"} file, _ := os.OpenFile("./file.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) # 创建一个可读写的文件对象 defer file.Close() writer := bufio.NewWriter(file) # 使用bufio创建一个新的写入器 for _, line := range lines { _, _ = writer.WriteString(line+"\n") # 写入器写入字符串 } writer.Flush() # 将写入器缓存中未写完的数据写入 }

http相关

shell
package main import ( "fmt" "io" "net/http" "time" ) func main() { req, _ := http.NewRequest("GET", "http://preaim.shoval.cn/", nil) # 创建一个http请求 req.Header.Set("Cache-Control", "no-cache") client := &http.Client{Timeout: time.Second * 10} # 创建一个http客户端 resp, _ := client.Do(req) # 使用客户端发送请求 body, _ := io.ReadAll(resp.Body) # 获取返回内容 defer resp.Body.Close() fmt.Printf("%s\n", body) # 打印内容 }