函数 🍞
基本语法
特点
func funcName(inputParams)(outputParams){
...
}
- 输出参数和输出参数都可以省略
- 多个相邻的相同类型参数可以简写
func add(a,b int) int {
return a + b
}
- 返回值可以有名字 相当于提前声明
func add(a, b int)(sum int) {
sum = a + b
return
}
// or
func add(a, b int)(sum int) {
sum := a + b
return sum
}
- 不支持默认值参数(=..)的写法不可以,不支持函数重载
- 不支持非匿名函数的嵌套
多值返回:一般把错误类型放最后
形参到实参仍然是值拷贝
不定参数
param ... type 声明不确定数目的多个类型相同的参数
必须是最后一个参数
相当于切片 也可以传切片... 但不能传数组!
但是形参为不定参数的函数 与 相应形参为slice的参数 类型不相同
func sum(arr ...int)(sum int) {
for _, v := range arr {
sum += v
}
return
}
slice := []int{1,2,3}
arr := [...]int{1,2,3}
sum(slice...)
函数签名
函数类型 取决于参数类型和返回值类型
只要类型、个数、次序相同,类型就相同 名字不重要
可以用type定义函数类型
函数类型变量是指向函数第一句话的指针
type Op func(int, int) int
func do(f Op, a, b int) int {
return f(a, b)
}
func main() {
a:= do(add, 1, 2)
fmt.Println(a)
s := do(sub, 1, 2)
fmt.Println(s)
}
匿名函数
有名字的函数可以直接赋值
f := sum
c := f(1,2)
所有使用函数类型变量的地方都可以替换成匿名函数
右值、参数、返回值
var sum = func(a,b int) int {
return a + b
}
func wrap (op string) func(int, int) int {
switch op {
case "add":
return func(a, b int) int {
return a + b
}
case "sub":
return func(a, b int) int {
return a - b
}
default:
return nil
}
}
defer
延迟调用,注册一个延迟调用栈(FILO),在父函数返回前被执行
func main() {
defer func() {
println("first")
}()
defer func() {
println("second")
}()
println("function body")
}
// 结果
// fuction body
// second
// first
注意
注意:
- defer后面必须是函数或者方法的调用
- 参数是值传递,不会改变值
- 必须先注册后执行、必须在return之前注册
- 如果用os.Exit(1)退出进程,则不会执行注册的defer函数
func main() {
defer func() {
println("defer")
}()
println("function body")
os.Exit(1)
}
// 结果
// func body
// exit status 1
用途
用于资源释放 可以有效避免资源泄露
func CopyFile(dst, src string) (w int64, err Error) {
src, err := os.Open(src)
if err != nil {
return
}
dst, err := os.Create(dst)
if err != nil {
src.Close()
return
}
w. err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
易漏:关闭文件
func CopyFile(dst, src string) (w int64, err Error) {
src, err := os.Open(src)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dst)
if err != nil {
return
}
defer dst.Close()
w. err = io.Copy(dst, src)
return
}
🍹
一般放在错误处理之后!
不要放在循环内部
相较于普通函数调用有一定的性能损耗
不要对有名返回值做操作
闭包
理解闭包
闭包是函数+引用环境
一般是由匿名函数+外部函数局部变量/全局变量组成 此时外部变量会被分配到堆上
不同引用环境
对函数局部变量
参数和局部变量
多个闭包对应多个副本,值不会相互影响
func fa(a int) func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1)
g := fa(1)
println(f(1))
println(f(1))
println(g(1))
println(g(1))
}
对全局变量
共享一个全局变量
// 声明一组变量
var (
a = 0
)
func fa() func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1)
g := fa(1)
println(f(1))
println(f(1))
println(g(1))
println(g(1))
}
错误处理
panic & recover
panic(i interface{})
recover() interface{}
引发panic:程序主动调用 + 运行时错误
处理:立即返回,逐层执行defer语句,逐层打印函数调用堆栈 直至被recover捕获或者退到最外层
panic
panic:空接口,任意类型的变量都可以传给panic
除了在函数内部,也可以在defer里面调用panic 会被之后defer捕获
正常情况下只会有一个panic
如果有多个 从第二个开始都是来自defer的
recover
通常和defer一起使用
🍸
defer的用法是后面加上函数或者方法的调用
不能直接放在defer后面
但是可以放在函数体内,而且必须是直接调用
// 错误 不能放在defer后面
defer recover()
// 错误 必须直接调用
defer fmt.Println(recover())
defer func() {
func {
recover()
}()
}()
// 成功
defer func() {
fmt.Println("recover")
recover()
}()
func except() {
recover()
}
defer except()
如果有多个panic(来自defer) 只会捕捉到最后一个
不能捕获内部新启动的goroutine抛出的panic
error
是一种接口
type error interface {
Error() string
}
常用的封装
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
func New(text string) error {
return &errorString{text}
}
也可以自己封装 只要符合接口就可以
最佳实践
- 通常作为函数的最后一个返回值
- 函数返回后先处理 err != nil
- defer应该放到err后面
- 在错误逐级向上传递的过程中,应当不断补充和丰富错误信息,而不是简单的抛出
🥛
- 如果错误发生会导致程序不能继续运行 就用panic
- 如果错误发生,但是还是可以运行,就用error抛出,或者在非关键分支使用recover捕获panic
底层实现
函数
- 函数调用者负责环境准备,为参数和返回值开辟栈空间,以及回收栈空间
- 调用者负责寄存器的保存和恢复
- 多值返回的实现原理是,返回值都在栈上,预先分配空间,返回前拷贝到这个空间
闭包
type Closure struct {
// 匿名函数指针
F uintptr
// 对外部环境变量的引用集合,没有修改外部变量时会被优化为值传递
env *Type
}