函数
函数声明
go
/*
func func_name(parameter-list) (result-list) {}
*/
func x0(a, b int, c string) (x, y int, z string) {
return // 等同于 return x, y, z,称为裸返回(按照返回值顺序返回)
}
- 函数的参数传递是
值传递的,函数接收到的是实参的副本- 存在多个相同类型的参数时,可以使用简写
x, y int- 可以给返回值进行声明,声明为
局部变量,并会根据类型初始化为零值- 若已经给返回值进行变量声明,那么函数中可以直接使用
return返回- 当两个函数拥有相同的形参列表和返回列表时,那么这两个函数的
类型或签名是相同的
异常处理
go
func main() {
if err := waitForServer("https://github1.com", 10*time.Second, 2*time.Second); err != nil {
fmt.Printf("waitForServer: %s\n", err)
}
}
/*
url: 请求的 url
timeout: 全部请求超时间隔
requestTimeout: 每次请求的间隔
*/
func waitForServer(url string, timeout time.Duration, requestTimeout time.Duration) error {
deadline := time.Now().Add(timeout)
client := http.Client{
Timeout: requestTimeout, // http client 设置超时时间
}
for i := 0; time.Now().Before(deadline); i++ {
_, err := client.Head(url) // 只获取响应头信息,不获取响应体
if err == nil {
return nil
}
fmt.Printf("server not responding (%s), retrying...\n", err)
time.Sleep(time.Second << i) // 指数级增长
}
return fmt.Errorf("server %s failed to respond after %s\n", url, timeout) // 创建格式化错误信息
}
error是内置的接口类型,当其值为非空值,意味着失败;为 nil 意味着成功- Go 语言中使用
普通的值来表示异常来报告错误,使用fmt.Errorf()构建新的 error 错误值
匿名函数
函数字面量就像函数声明,但在 func 关键字后面没有函数的名称,它是一个表达式,它的值被称为匿名函数。
go
func main() {
// 匿名函数作为参数
s := strings.Map(func(r rune) rune {
return r + 1
}, "HAL-9000")
fmt.Printf("map: %#v\n", s)
a := anonymousFunc() // 获取匿名函数的引用,因为返回的是 func() int 类型
fmt.Println(a()) // 此时匿名函数 a 中的局部变量 x=1
fmt.Println(a()) // 此时匿名函数 a 中的局部变量 x=2
func(x int) { fmt.Printf("x: %d\n", x) }(1) // 构建匿名函数并调用
}
/*
匿名函数作为返回值类型
*/
func anonymousFunc() func() int {
var x int // 能够被内层的匿名函数访问和更新
return func() int {
x++
return x * x
}
}迭代变量捕获
go
/*
演示 Go 的循环迭代变量捕获问题
*/
func test1() {
var funcs []func()
nums := []int{1, 2, 3, 4}
for _, num := range nums {
// num := num // 避免迭代变量捕获问题
funcs = append(funcs, func() { // 构建匿名函数的 slice,for 循环结束后 num 的值为 4,num 共用一个内存地址,每次都是更新值
fmt.Printf("num: %d\n", num) // 匿名函数中记录的 num 不是值,是内存地址
})
}
for _, f := range funcs {
f() // 执行匿名函数时,num 的值随着迭代结束已经变为 4
}
}
- 迭代的变量 num 使用的是
同一个内存地址,每次循环都是更新内存地址对应的值- 在第一个循环中生成的所有匿名函数都
共享相同的循环变量 num(逃逸到堆中),且 num 记录的是迭代变量的内存地址,而不是值- 在匿名函数执行时,num 中存储的值等于最后一次迭代的值,所以打印的都是相同的数字 4
- 只需使用
局部变量拷贝循环变量的值,这样在匿名函数中局部变量 num 记录的就是某一时刻循环变量的值,而不是循环变量的地址
变长函数
变长函数在被调用的时候可以有 可变的参数个数,其本质就是一个某种类型的 slice(Java 中的可变参数其实是个数组)。
go
func test(a ...int) {
fmt.Printf("type: %T", a) // []int
}
func main() {
test(1, 2, 3)
a := []int{1, 2, 3}
test(a...) // 对于已经存在的一个 slice,通过添加 ... 实现对变长函数的调用
}延迟函数
defer 用于延迟执行一个函数调用,该函数会在 当前函数返回之前 被调用执行,无论该函数是正常返回还是发生异常。存在多个 defer 语句时,调用的顺序是 后进先出。常用于 关闭文件、资源释放、释放锁及跟踪函数执行。
go
func test3() {
resp, err := http.Get("https://baidu.com")
if err != nil {
fmt.Printf("error: %s", err.Error())
}
defer resp.Body.Close() // 关闭资源
}
func test4() {
fmt.Println("step1")
defer fmt.Println("step2")
defer fmt.Println("step3")
// step1 -> step3 -> step2
}
func test5() {
defer test6("test5")() // 会获取函数但会等到 return 前再执行
time.Sleep(2 * time.Second)
fmt.Println("test5 do something...")
}
func test6(msg string) func() {
now := time.Now() // 会在一开始获取函数值时就计算
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s(%s)", msg, time.Since(now)) // time.Since(now) 会在 test5 返回时才会计算
}
}
- 执行
defer test6("test5")()时,会先获取函数值(即 test6 中返回的匿名函数)但不会立马执行,会等到 test5() 方法返回前再执行- defer 函数执行时,其函数中的变量(即 time.Since(now))才会被确定,并在函数返回前保持不变
宕机与恢复
当程序遇到无法处理的错误或异常情况时,可以使用 panic 函数引发 panic。Panic 会导致程序立即停止执行,并开始执行调用栈的展开过程,在其展开过程中执行 defer 函数,最后程序终止。
recover() 是一个内建函数,能够处理 panic 异常,其只能在 defer 函数中使用,用于捕获和处理 panic 异常。
go
func test7() {
defer fmt.Println("defer print")
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("Something went wrong") // 宕机并输出日志信息和栈转储信息到 stdout
fmt.Println("This line will not be printed") // 不会被执行
// Recovered from panic: Something went wrong -> defer print
}
- 在使用
recover()函数时,Panic 不会导致程序停止执行,会被recover()捕获和处理recover()只能在 defer 函数中执行,存在多个 defer 函数时仍然按照后进先出的原则调用
🎈🎈