经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Go 函数详解
来源:cnblogs  作者:LB477  时间:2021/5/24 10:55:28  对本文有异议

一、函数基础

  • 函数由函数声明关键字 func、函数名、参数列表、返回列表、函数体组成
  • 函数是一种类型。函数类型变量可以像其他类型变量一样使用,可以作为其他函数的参数或返回值,也可以直接调用执行
  • 函数名首字母大小写决定了其包可见性
  • 参数和返回值需用()包裹,如果返回值是一个非命名的参数,则可省略。函数体使用{}包裹,且{必须位于同行行尾

1. 基本使用

  1. // 1. 可以没有输入参数,也可以没有返回值(默认返回 0)
  2. func A() {
  3. ...
  4. }
  5. // 2. 多个相邻的相同类型参数可以使用简写模式
  6. func B(a, b int) int {
  7. return a + b
  8. }
  9. // 3. 支持有名的返回值
  10. func C(a, b int) (sum int) {
  11. // sum 相当于函数体内的局部变量,初始化为零值
  12. sum = a + b
  13. return // 可以不带 sum
  14. }
  15. // 4. 不支持默认值参数
  16. // 5. 不支持函数重载
  17. // 6. 不支持函数嵌套定义,但支持嵌套匿名函数
  18. func D(a, b int) (sum int) {
  19. E := func(x, y int) int {
  20. return x + y
  21. }
  22. return E(a, b)
  23. }
  24. // 7. 支持多值返回(一般将错误类型作为最后一个返回值)
  25. func F(a, b int) (int, int) {
  26. return b, a
  27. }
  28. // 8. 函数实参到形参的传递永远是**值拷贝**
  29. func G(a *int) { // a 是实参指针变量的副本,和实参指向同一个地址
  30. *a += 1
  31. }

2. 不定参数

  1. // 1. 不定参数类型相同
  2. // 2. 不定参数必须是函数的最后一个参数
  3. // 3. 不定参数在函数体内相当于切片
  4. func sum(arr ...int) (sum int) {
  5. for _, v := range arr { // arr 相当于切片,可使用 range访问
  6. sum += v
  7. }
  8. return
  9. }
  10. // 4. 可以将切片传递给不定参数
  11. array := [...]int{1, 2, 3, 4} // 不能将数组传递给不定参数
  12. slice := []int{1, 2, 3, 4}
  13. sum(slice...) // 切片名后要加 ...
  14. // 5. 形参为不定参数的函数和形参为切片的函数类型不同
  15. func suma(arr ...int) (sum int) {
  16. for v := range arr {
  17. sum += v
  18. }
  19. return
  20. }
  21. func sumb(arr []int) (sum int) {
  22. for v := range arr {
  23. sum += v
  24. }
  25. return
  26. }
  27. fmt.Printf("%T\n", suma) // func(...int) int
  28. fmt.Printf("%T", sumb) // func([]int) int

3. 函数类型

函数类型又叫函数签名:函数定义行去掉函数名、参数名和 {

  1. func add(a, b int) int { return a + b }
  2. func sub(x int, y int) (c int) { c = x - y; return }
  3. fmt.Printf("%T", add) // func(int, int) int
  4. fmt.Printf("%T", sub) // func(int, int) int

可以使用 type 定义函数类型。函数类型变量和函数名都可以看做指针变量,该指针指向函数代码的开始位置

  1. func add(a, b int) int { return a + b }
  2. func sub(a, b int) int { return a - b }
  3. type Op func(int, int) int // 定义一个函数类型:输入两个 int,返回一个 int
  4. func do(f Op, a, b int) int {
  5. t := f
  6. return t(a, b)
  7. }
  8. fmt.Println(do(add, 1, 2)) // 3
  9. fmt.Println(do(sub, 1, 2)) // -1

4. 匿名函数

  1. // 1. 直接赋值给函数变量
  2. var sum = func(a, b int) int {
  3. return a + b
  4. }
  5. func do(f func(int, int) int, a, b int) int {
  6. return f(a, b)
  7. }
  8. // 2. 作为返回值
  9. func getAdd() func(int, int) int {
  10. return func(a, b int) int {
  11. return a + b
  12. }
  13. }
  14. func main() {
  15. // 3. 直接被调用
  16. defer func() {
  17. if err:= recover(); err != nil {
  18. fmt.Println(err)
  19. }
  20. }()
  21. sum(1, 2)
  22. getAdd()(1 , 2)
  23. // 4. 作为实参
  24. do(func(x, y int) int { return x + y }, 1, 2)
  25. }

二、函数高级

1. defer

可注册多个延迟调用函数,以先进后出的顺序执行。常用于保证资源最终得到回收释放

  1. func main() {
  2. // defer 后跟函数或方法调用,不能是语句
  3. defer func() {
  4. println("first")
  5. }()
  6. defer func() {
  7. println("second")
  8. }()
  9. println("main")
  10. }
  11. // main
  12. // second
  13. // first

defer 函数的实参在注册时传递,后续变更无影响

  1. func f() int {
  2. a := 1
  3. defer func(i int) {
  4. println("defer i =", i)
  5. }(a)
  6. a++
  7. return a
  8. }
  9. print(f())
  10. // defer i = 1
  11. // 2

defer 若位于 return 后,则不会执行

  1. func main() {
  2. println("main")
  3. return
  4. defer func() {
  5. println("first")
  6. }()
  7. }
  8. // main

若主动调用os.Exit(int)退出进程,则不会执行 defer

  1. func main() {
  2. defer func() {
  3. println("first")
  4. }()
  5. println("main")
  6. os.Exit(1)
  7. }
  8. // main

关闭资源例子

  1. func CopyFile(dst, src string) (w int64, err error) {
  2. srcFile, err := os.Open(src)
  3. if err != nil {
  4. return
  5. }
  6. // defer 一般放在错误检查语句后面。若位置不当可能造成 panic
  7. defer srcFile.Close()
  8. dstFile, err := os.Create(dst)
  9. if err != nil {
  10. return
  11. }
  12. defer dstFile.Close()
  13. w, err = io.Copy(dstFile, srcFile)
  14. return
  15. }

defer 使用注意事项:

  • defer 会延迟资源的释放
  • 尽量不要放在循环语句中
  • defer 相对于普通函数调用需要间接的数据结构支持,有一定性能损耗
  • defer 中最好不要对有名返回值进行操作

2. 闭包

  • 闭包是由函数及其相关引用环境组合成的实体。一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成
  • 闭包对闭包外的环境引入是直接引用:编译器检测到闭包,会将闭包引用的外部变量分配到堆上
  • 闭包是为了减少全局变量,在函数调用的过程中隐式地传递共享变量。但不够清晰,一般不建议用
  • 对象是附有行为的数据,而闭包是附有数据的行为。类在定义时已经显式地集中定义了行为,但闭包中的数据没有显式地集中声明的地方
  1. // fa 返回的是一个闭包:形参a + 匿名函数
  2. func fa(a int) func(i int) int {
  3. return func(i int) int {
  4. println(&a, a)
  5. a = a + i
  6. return a
  7. }
  8. }
  9. func main() {
  10. f := fa(1) // f 使用的 a 是 0xc0000200f0
  11. g := fa(1) // g 使用的 a 是 0xc0000200f8
  12. // f、g 引用的闭包环境中的 a 是函数调用产生的副本:每次调用都会为局部变量分配内存
  13. println(f(1))
  14. println(f(1)) // 闭包共享外部引用,因此修改的是同一个副本
  15. println(g(1))
  16. println(g(1))
  17. }
  18. // 0xc0000200f0 1
  19. // 2
  20. // 0xc0000200f0 2
  21. // 3
  22. // 0xc0000200f8 1
  23. // 2
  24. // 0xc0000200f8 2
  25. // 3

闭包引用全局变量(不推荐)

  1. var a = 0
  2. // fa 返回的是一个闭包:全局变量a + 匿名函数
  3. func fa() func(i int) int {
  4. return func(i int) int {
  5. println(&a, a)
  6. a = a + i
  7. return a
  8. }
  9. }
  10. func main() {
  11. f := fa()
  12. g := fa()
  13. // f、g 引用的闭包环境中的 a 是同一个
  14. println(f(1))
  15. println(g(1))
  16. println(f(1))
  17. println(g(1))
  18. }
  19. // 0x511020 0
  20. // 1
  21. // 0x511020 1
  22. // 2
  23. // 0x511020 2
  24. // 3
  25. // 0x511020 3
  26. // 4

同一个函数返回的多个闭包共享该函数的局部变量

  1. func fa(a int) (func(int) int, func(int) int) {
  2. println(&a, a)
  3. add := func(i int) int {
  4. a += i
  5. println(&a, a)
  6. return a
  7. }
  8. sub := func(i int) int {
  9. a -= i
  10. println(&a, a)
  11. return a
  12. }
  13. return add, sub
  14. }
  15. func main() {
  16. f, g := fa(0) // f、g 使用的 a 都是 0xc0000200f0
  17. s, k := fa(0) // s、k 使用的 a 都是 0xc0000200f8
  18. println(f(1), g(2))
  19. println(s(1), k(2))
  20. }
  21. // 0xc0000200f0 0
  22. // 0xc0000200f8 0
  23. // 0xc0000200f0 1
  24. // 0xc0000200f0 -1
  25. // 1 -1
  26. // 0xc0000200f8 1
  27. // 0xc0000200f8 -1
  28. // 1 -1

三、错误处理

1. 错误和异常

  • 广义的错误:发生非期望的行为
  • 狭义的错误:发生非期望的己知行为
    • 这里的己知是指错误类型是预料并定义好的
  • 异常:发生非期待的未知行为,又被称为未捕获的错误
    • 这里的未知是指错误的类型不在预先定义的范围内
    • 程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理,而是由操作系统进行异常处理。如 C 语言的 Segmentation Fault

错误分类

Go 不会出现 untrapped error,只需处理 runtime errors 和程序逻辑错误

Go 提供两种错误处理机制

  • 通过 panic 打印程序调用栈,终止程序来处理错误
  • 通过函数返回错误类型的值来处理错误

Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但有些错误行为需要在运行期才能检测出来,此种错误行为将导致程序异常退出。建议:

  • 若程序发生的错误导致程序不能继续执行,此时程序应该主动调用 panic
  • 若程序发生的错误能够容错继续执行,此时应该使用 error 返回值的方式处理,或在非关键分支上使用 recover 捕获 panic

2. panic 和 recover

  1. panic(i interface{}) // 主动抛出错误
  2. recover() interface{} // 捕获抛出的错误
  • 引发panic情况:①主动调用panic;②程序运行时检测抛出运行时错误
  • panic 后,程序会从当前位置返回,逐层向上执行 defer 语句,逐层打印函数调用栈,直到被 recover 捕获或运行到最外层函数退出
  • 参数为空接口类型,可以传递任意类型变量
  • defer 中也可以 panic,能被后续 defer 捕获
  • recover 只有在 defer 函数体内被调用才能捕获 panic,否则返回 nil
  1. // 以下场景捕获失败
  2. defer recover()
  3. defer fmt.Println(recover())
  4. defer func() {
  5. func() { // 两层嵌套
  6. println("defer inner")
  7. recover()
  8. }()
  9. }()
  10. // 以下场景捕获成功
  11. defer func() {
  12. println("defer inner")
  13. recover()
  14. }()
  15. func except() {
  16. recover()
  17. }
  18. func test() {
  19. defer except()
  20. painc("test panic")
  21. }

可以同时有多个 panic(只会出现在 defer 里),但只有最后一次 panic 能被捕获

  1. func main() {
  2. defer func() {
  3. if err := recover(); err != nil {
  4. fmt.Println(err)
  5. }
  6. fmt.Println(recover())
  7. }()
  8. defer func() {
  9. panic("first defer panic")
  10. }()
  11. defer func() {
  12. panic("second defer panic")
  13. }()
  14. panic("main panic")
  15. }
  16. // first defer panic
  17. // <nil>

包中 init 函数引发的 panic 只能在 init 函数中捕获(init 先于 main 执行)

函数不能捕获内部新启动的 goroutine 抛出的 panic

  1. func do() {
  2. // 不能捕获 da 中的 panic
  3. defer func() {
  4. if err := recover(); err != nil {
  5. fmt.Println(err)
  6. }
  7. }()
  8. go da()
  9. time.Sleep(3 * time.Second)
  10. }
  11. func da() {
  12. panic("panic da")
  13. }

3. error

Go 内置错误接口类型 error。任何类型只要实现Error() string方法,都可以传递 error 接口类型变量???

  1. type error interface {
  2. Error() string
  3. }

使用 error:

  • 在多个返回值的函数中,error 作为函数最后一个返回值
  • 若函数返回 error 类型变量,先处理error != nil的异常场景,再处理其他流程
  • defer 放在 error 判断的后面

四、底层实现

TODO

原文链接:http://www.cnblogs.com/lb477/p/14790947.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号