()
包裹,如果返回值是一个非命名的参数,则可省略。函数体使用{}
包裹,且{
必须位于同行行尾// 1. 可以没有输入参数,也可以没有返回值(默认返回 0) func A() { ... } // 2. 多个相邻的相同类型参数可以使用简写模式 func B(a, b int) int { return a + b } // 3. 支持有名的返回值 func C(a, b int) (sum int) { // sum 相当于函数体内的局部变量,初始化为零值 sum = a + b return // 可以不带 sum } // 4. 不支持默认值参数 // 5. 不支持函数重载 // 6. 不支持函数嵌套定义,但支持嵌套匿名函数 func D(a, b int) (sum int) { E := func(x, y int) int { return x + y } return E(a, b) } // 7. 支持多值返回(一般将错误类型作为最后一个返回值) func F(a, b int) (int, int) { return b, a } // 8. 函数实参到形参的传递永远是**值拷贝** func G(a *int) { // a 是实参指针变量的副本,和实参指向同一个地址 *a += 1 }
// 1. 不定参数类型相同 // 2. 不定参数必须是函数的最后一个参数 // 3. 不定参数在函数体内相当于切片 func sum(arr ...int) (sum int) { for _, v := range arr { // arr 相当于切片,可使用 range访问 sum += v } return } // 4. 可以将切片传递给不定参数 array := [...]int{1, 2, 3, 4} // 不能将数组传递给不定参数 slice := []int{1, 2, 3, 4} sum(slice...) // 切片名后要加 ... // 5. 形参为不定参数的函数和形参为切片的函数类型不同 func suma(arr ...int) (sum int) { for v := range arr { sum += v } return } func sumb(arr []int) (sum int) { for v := range arr { sum += v } return } fmt.Printf("%T\n", suma) // func(...int) int fmt.Printf("%T", sumb) // func([]int) int
函数类型又叫函数签名:函数定义行去掉函数名、参数名和 {
func add(a, b int) int { return a + b } func sub(x int, y int) (c int) { c = x - y; return } fmt.Printf("%T", add) // func(int, int) int fmt.Printf("%T", sub) // func(int, int) int
可以使用 type 定义函数类型。函数类型变量和函数名都可以看做指针变量,该指针指向函数代码的开始位置
func add(a, b int) int { return a + b } func sub(a, b int) int { return a - b } type Op func(int, int) int // 定义一个函数类型:输入两个 int,返回一个 int func do(f Op, a, b int) int { t := f return t(a, b) } fmt.Println(do(add, 1, 2)) // 3 fmt.Println(do(sub, 1, 2)) // -1
// 1. 直接赋值给函数变量 var sum = func(a, b int) int { return a + b } func do(f func(int, int) int, a, b int) int { return f(a, b) } // 2. 作为返回值 func getAdd() func(int, int) int { return func(a, b int) int { return a + b } } func main() { // 3. 直接被调用 defer func() { if err:= recover(); err != nil { fmt.Println(err) } }() sum(1, 2) getAdd()(1 , 2) // 4. 作为实参 do(func(x, y int) int { return x + y }, 1, 2) }
可注册多个延迟调用函数,以先进后出的顺序执行。常用于保证资源最终得到回收释放
func main() { // defer 后跟函数或方法调用,不能是语句 defer func() { println("first") }() defer func() { println("second") }() println("main") } // main // second // first
defer 函数的实参在注册时传递,后续变更无影响
func f() int { a := 1 defer func(i int) { println("defer i =", i) }(a) a++ return a } print(f()) // defer i = 1 // 2
defer 若位于 return 后,则不会执行
func main() { println("main") return defer func() { println("first") }() } // main
若主动调用os.Exit(int)
退出进程,则不会执行 defer
func main() { defer func() { println("first") }() println("main") os.Exit(1) } // main
关闭资源例子
func CopyFile(dst, src string) (w int64, err error) { srcFile, err := os.Open(src) if err != nil { return } // defer 一般放在错误检查语句后面。若位置不当可能造成 panic defer srcFile.Close() dstFile, err := os.Create(dst) if err != nil { return } defer dstFile.Close() w, err = io.Copy(dstFile, srcFile) return }
defer 使用注意事项:
// fa 返回的是一个闭包:形参a + 匿名函数 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) // f 使用的 a 是 0xc0000200f0 g := fa(1) // g 使用的 a 是 0xc0000200f8 // f、g 引用的闭包环境中的 a 是函数调用产生的副本:每次调用都会为局部变量分配内存 println(f(1)) println(f(1)) // 闭包共享外部引用,因此修改的是同一个副本 println(g(1)) println(g(1)) } // 0xc0000200f0 1 // 2 // 0xc0000200f0 2 // 3 // 0xc0000200f8 1 // 2 // 0xc0000200f8 2 // 3
闭包引用全局变量(不推荐)
var a = 0 // fa 返回的是一个闭包:全局变量a + 匿名函数 func fa() func(i int) int { return func(i int) int { println(&a, a) a = a + i return a } } func main() { f := fa() g := fa() // f、g 引用的闭包环境中的 a 是同一个 println(f(1)) println(g(1)) println(f(1)) println(g(1)) } // 0x511020 0 // 1 // 0x511020 1 // 2 // 0x511020 2 // 3 // 0x511020 3 // 4
同一个函数返回的多个闭包共享该函数的局部变量
func fa(a int) (func(int) int, func(int) int) { println(&a, a) add := func(i int) int { a += i println(&a, a) return a } sub := func(i int) int { a -= i println(&a, a) return a } return add, sub } func main() { f, g := fa(0) // f、g 使用的 a 都是 0xc0000200f0 s, k := fa(0) // s、k 使用的 a 都是 0xc0000200f8 println(f(1), g(2)) println(s(1), k(2)) } // 0xc0000200f0 0 // 0xc0000200f8 0 // 0xc0000200f0 1 // 0xc0000200f0 -1 // 1 -1 // 0xc0000200f8 1 // 0xc0000200f8 -1 // 1 -1
Go 不会出现 untrapped error,只需处理 runtime errors 和程序逻辑错误
Go 提供两种错误处理机制
Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但有些错误行为需要在运行期才能检测出来,此种错误行为将导致程序异常退出。建议:
panic(i interface{}) // 主动抛出错误 recover() interface{} // 捕获抛出的错误
// 以下场景捕获失败 defer recover() defer fmt.Println(recover()) defer func() { func() { // 两层嵌套 println("defer inner") recover() }() }() // 以下场景捕获成功 defer func() { println("defer inner") recover() }() func except() { recover() } func test() { defer except() painc("test panic") }
可以同时有多个 panic(只会出现在 defer 里),但只有最后一次 panic 能被捕获
func main() { defer func() { if err := recover(); err != nil { fmt.Println(err) } fmt.Println(recover()) }() defer func() { panic("first defer panic") }() defer func() { panic("second defer panic") }() panic("main panic") } // first defer panic // <nil>
包中 init 函数引发的 panic 只能在 init 函数中捕获(init 先于 main 执行)
函数不能捕获内部新启动的 goroutine 抛出的 panic
func do() { // 不能捕获 da 中的 panic defer func() { if err := recover(); err != nil { fmt.Println(err) } }() go da() time.Sleep(3 * time.Second) } func da() { panic("panic da") }
Go 内置错误接口类型 error。任何类型只要实现Error() string
方法,都可以传递 error 接口类型变量???
type error interface { Error() string }
使用 error:
error != nil
的异常场景,再处理其他流程TODO