ch14、函数 - 可变参数和defer
1、可变参数
传入的参数不需要指定个数,使用:varName …type表示
package function
import (
"testing"
)
func Sum(ops ...int) int {
ret := 0
for _, op := range ops {
ret += op
}
return ret
}
func TestVarParameter(t *testing.T) {
t.Log(Sum(1, 2, 3, 4, 5)) // 15
t.Log(Sum(1, 2, 3, 4)) // 10
}
2、defer 函数(延迟函数)
作用:延迟函数,一般是用于释放资源或者收尾工作;执行动作是在函数return之后,因此作为资源释放作用再好不过
package function
import (
"fmt"
"testing"
)
func Clear() {
fmt.Println("Clear resource...")
}
func TestDefer(t *testing.T) {
defer Clear()
fmt.Println("Start...")
panic("Error...") // defer仍然会执行
fmt.Println("after panic is executed?") // 不会被执行
}
defer 释放资源『避坑指南』:
- 资源释放动作一定紧跟资源使用(打开、连接)语句,不然defer可能不会被执行到,导致内存泄露
package function
import (
"os"
)
// 关闭文件
func read() error {
// 错误用法
f1, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
if err != nil {
return err
}
// 此时,defer还没执行到,提前return了,无效defer导致内存泄露
defer f1.Close()
// 正确用法,紧跟资源使用语句
f2, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
defer f2.Close()
if err != nil {
return err
}
return err
}
-
多个 defer 调用顺序:
LIFO(后入先出,先进后出),defer后的操作可以理解为压入栈中
package defertest
import (
"fmt"
"testing"
)
func TestExecuteSequence(t *testing.T) {
defer func() { fmt.Println("1") }()
defer func() { fmt.Println("2") }()
defer func() { fmt.Println("3") }()
t.Log(4)
}
/*
output:
4
3
2
1
*/
-
defer,return,return var(函数返回变量值,如return 1,但返回变量早就定义好了) 执行顺序:
首先return,其次return var,最后defer。defer可以修改函数最终返回值
注意:被defer的函数可以读取和修改带名称的返回值
package defertest
import (
"fmt"
"testing"
)
func deferFunc1(i int) (t int) {
t = i
defer func() {
t += 1
}()
return t
}
func deferFunc2(i int) int { // 没有返回参数,会直接返回t=1的值到临时变量
t := i
defer func() { // 最后会计算该函数,然后将值2返回,但是没有变量接受
t += 1
}()
return t
}
func deferFunc3(i int) (t int) {
defer func() { // 被defer的函数可以读取和修改带名称的返回值
t += i
}()
return 1
}
// 给返回值指定了一个名称叫做t, return时会自动将函数体内部t作为返回值
// 其实本质就是提前定义了一个局部变量t, 在函数体中使用的t就是这个局部变量, 返回的也是这个局部变量
func deferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t) // defer在定义时变量就会被确定,此处的t是取的int默认值0,即入参i=0
t = 0
return 1 // 为什么t会变成1?返回变量早就被声明了,所以会是1
}
func TestDeferFunc(t *testing.T) {
// 猜猜下面输出的内容和顺序
fmt.Println(deferFunc1(1))
fmt.Println(deferFunc2(1))
fmt.Println(deferFunc3(1))
deferFunc4()
}
/*
output:
2
1
2
0
1
*/
-
deferred函数的参数(是指入参):
被 deferred 函数的参数在defer声明时就已经被确定
package defertest
import (
"fmt"
"testing"
)
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func TestCalcParameter(t *testing.T) {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0 // defer不会使用该参数值
return
}
/*
output:
10 1 2 3
1 1 3 4
*/
3、init函数
golang里面有两个保留的函数:
- init函数(能够应用于所有的package)
- main函数(只能应用于package main)
- 这两个函数在定义时不能有任何的参数和返回值
- go程序会自动调用init()和main(),所以**
不能
**在任何地方调用这两个函数 - package main必须包含一个main函数, 但是每个package中的init函数都是可选的
- 一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,**强烈建议用户在一个package中每个文件只写一个init函数
- 单个包中代码执行顺序如下
main包-->常量-->全局变量-->init函数-->main函数-->Exit
package main
import "fmt"
const constValue = 998 // 1
var gloalVarValue int = abc() // 2
func init() { // 3
fmt.Println("执行main包中main.go中init函数")
}
func main() { // 4
fmt.Println("执行main包中main.go中main函数")
}
func abc() int {
fmt.Println("执行main包中全局变量初始化")
return 998
}
- 多个包之间代码执行顺序如下
- init函数的作用
- init函数用于处理当前文件的初始化操作, 在使用某个文件时的一些准备工作应该放到这里