定义
函数是结构化编程的最小模块单元,将复杂的算法过程分解为若干较小任务,代码复用和测试的基本单元;关键字func用于定义函数
参数
go不支持有默认值的可选参数,不支持命名实参,调用时,必须按签名顺序传递指定类型和数量的实参
参数列表中,相邻的同类型参数可合并
参数可视为函数局部变量,不能在相同层次定义同名变量
形参是指函数定义中的参数,实参则是函数调用时传递的参数;形参类似函数的局部变量,而实参则是函数外部对象
不管是指针,引用类型,还是其他类型参数,都是值拷贝传递;在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存
如果函数的参数过多,建议将其重构为一个复杂结构类型,也算是变相实现可选参数和命名实参功能
package main import ( "fmt" "log" "time" ) type serverOption struct { address string port int path string timeount time.Duration log *log.Logger } func newOption() *serverOption { return &serverOption{ address: "0.0.0.0", port: 8080, path: "/var/test", timeount: time.Second * 5, log: nil, } } func server(option *serverOption) { fmt.Println(option) } func main() { opt := newOption() opt.port = 8085 server(opt) }
变参
变参本质上就是一个切片,只能接收一到多个同类型参数,且必须放在列表尾部
package main import ( "fmt" ) func test(s string, a ...int) { fmt.Printf("%T,%v ", a, a) } func main() { test("abc", 1, 2, 3, 4) }
输出
[]int,[1 2 3 4]
将切片作为变参时,需进行展开操作,如果是数组,先将其转换为切片
func test(a ...int) { fmt.Println(a) } func main() { a := [3]int{10, 20, 30} test(a[:]...) }
返回值
有返回值的函数,必须有明确的return终止语句
多返回值列表必须括号
可以对返回值命名
func div(x, y int) (int, error) { if y == 0 { err := errors.New("division by zero") return -1, err } return x / y, nil } func main() { result, err := div(4, 0) if err != nil { fmt.Println(err) } fmt.Println(result) }
匿名函数
匿名函数是指没有定义名字符号的函数
在函数内部定义匿名函数,匿名函数可直接调用,保存到变量,作为参数或返回值
直接执行
func main() { func(s string) { fmt.Println(s) }("hello world") }
赋值给变量
func main() { add := func(x, y int) int { return x + y } result := add(1, 2) fmt.Println(result) }
作为参数
func test(f func()) { f() } func main() { test(func() { fmt.Println("hello") }) }
闭包
example
func test(x int) func() { return func() { fmt.Println(x) } } func main() { f := test(123) f() }
test返回的匿名函数会引用上下文环境变量x,在main中执行时,依然可正确读取x的值,这种现象称为闭包
正因为闭包通过指针引用环境变量,导致其生命周期延长,甚至被分配到堆内存
输出
0xc04204e080 2 0xc04204e080 2
main执行这两函数时,读取的环境变量i=2
怎么解决?
每次用不同的环境变量或传参赋值,让各自闭包环境不同
func test() []func() { var s []func() for i := 0; i < 2; i++ { x := i s = append(s, func() { fmt.Println(&x, x) }) } return s } func main() { for _, f := range test() { f() } }
延迟调用
defer向当前函数注册 稍后执行的函数调用,这些调用称为延迟调用,因为它们直到当前函数执行结束前才被执行,常用于资源释放,解除锁定,错误处理等
func main() { x, y := 1, 2 defer func(x int) { fmt.Println(x, y) //y为闭包引用 }(x) //注册时复制调用参数 x += 100 //对x的修改不用影响延时调用 y += 100 fmt.Println(x, y) }
输出
101 102 1 102
多个延迟注册按FILO次序执行
编译器通过插入额外指令来实现延迟调用执行,return和panic会终止当前函数流程,引发延迟调用
package main import ( "fmt" ) func test() (z int) { defer func() { fmt.Println("defer:", z) z += 100 }() return 1 //实际执行顺序:z=1,call defer,ret } func main() { fmt.Println("test:", test()) }
输出
defer: 1 test: 101
误用
延迟调用在函数结束时才被执行
案例:循环处理多个日志文件,不恰当的defer导致文件关闭时间延长
func main() { for i := 0; i < 1000; i++ { path := fmt.Sprintf("./log/%d.txt", i) f, err := os.Open(path) if err != nil { log.Println(err) continue } defer f.Close() } }
这个关闭操作在main函数执行结束才会执行,不会在循环中执行,应该重构为函数,将循环和处理算法分离
func main() { do := func(n int) { path := fmt.Sprintf("./log/%d.txt", n) f, err := os.Open(path) if err != nil { log.Println(err) return } defer f.Close() } for i := 0; i < 1000; i++ { do(i) } }
错误处理
error
标准库将error定义为接口类型,以便实现自定义错误类型
type errpr interface { Error() string }
自定义错误类型,根据错误类型判断
package main import ( "fmt" ) type DivError struct { x, y int } func (DivError) Error() string { return "division by zero" } func div(x, y int) (int, error) { if y == 0 { return 0, DivError{x, y} } return x / y, nil } func main() { _, err := div(5, 0) if err != nil { switch e := err.(type) { case DivError: fmt.Println(e, e.x, e.y) default: fmt.Println(e) } } }
panic,recover
panic会立即中断当前函数流程,执行延迟调用;在延迟调用中,recover能捕获并返回panic提交的错误对象
package main import ( "fmt" ) func main() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() panic("dead") fmt.Println("end") //永不执行 }