Go语法基础 - 关于函数的一切| 青训营

59 阅读5分钟

函数 🍞

基本语法

特点

func funcName(inputParams)(outputParams){ 
	...
}
  1. 输出参数和输出参数都可以省略
  2. 多个相邻的相同类型参数可以简写
func add(a,b int) int {
    return a + b
}
  1. 返回值可以有名字 相当于提前声明
func add(a, b int)(sum int) {
    sum = a + b
    return
}
// or
func add(a, b int)(sum int) {
    sum := a + b
    return sum
}
  1. 不支持默认值参数(=..)的写法不可以,不支持函数重载
  2. 不支持非匿名函数的嵌套

多值返回:一般把错误类型放最后

形参到实参仍然是值拷贝

不定参数

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

注意

注意:

  1. defer后面必须是函数或者方法的调用
  2. 参数是值传递,不会改变值
  3. 必须先注册后执行、必须在return之前注册
  4. 如果用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}
}

也可以自己封装 只要符合接口就可以

最佳实践

  1. 通常作为函数的最后一个返回值
  2. 函数返回后先处理 err != nil
  3. defer应该放到err后面
  4. 在错误逐级向上传递的过程中,应当不断补充和丰富错误信息,而不是简单的抛出

🥛

  • 如果错误发生会导致程序不能继续运行 就用panic
  • 如果错误发生,但是还是可以运行,就用error抛出,或者在非关键分支使用recover捕获panic

底层实现

函数

  1. 函数调用者负责环境准备,为参数和返回值开辟栈空间,以及回收栈空间
  2. 调用者负责寄存器的保存和恢复
  3. 多值返回的实现原理是,返回值都在栈上,预先分配空间,返回前拷贝到这个空间

闭包

type Closure struct {
    // 匿名函数指针
    F uintptr 
  	// 对外部环境变量的引用集合,没有修改外部变量时会被优化为值传递 
    env *Type
}