4. 函数

4.1 声明

普通函数声明

func name(params) (returns) { // 如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
    // name 小写为私有函数,大写开头则为公有函数
    ...
}

func add(x int, y int) int   { return x + y }
func sub(x, y int) (z int)   { z = x - y; return }
func first(x int, _ int) int { return x }
func zero(int, int) int      { return 0 }
fmt.Printf("%T\n", add)   // "func(int, int) int"
fmt.Printf("%T\n", sub)   // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero)  // "func(int, int) int"

如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型。下面 2 个声明是等价的:

func f(i, j, k int, s, t string) { /* ... */ }

func f(i int, j int, k int, s string, t string) { /* ... */ }

可变参数

// args ...type
// 任意类型的可变参数
args ...interface{}

如果要在多个可变参数中传递参数,可以在传递时在可变参数变量中添加 ...,将切片中的元素进行传递,而不是传递可变参数变量本身,例如切片使用的 append

函数的返回值

Go 语言支持多返回值,多返回值能方便地获得函数执行后的多个返回参数,例如:

conn, err := connectToNetwork()

// 同类型返回值,可读性差
func typedTwoValues() (int, int) {
    return 1, 2
}

// 带有变量名的返回值,可读性好
func namedRetValues() (a, b int) {
    a = 1
    b = 2
    return
}

// 混用会编译错误
func namedRetValues() (a, b int, int)

调用函数

// 返回值变量列表 = 函数名(参数列表)
result := add(1, 1)

4.2 函数值传递和引用传递

Go 语言默认使用按值传递来传递参数,也就是传递参数的副本,像基本类型,结构体都是值传递。

引用传值的话就是传递给函数的是一个指针,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显示的指出指针)。

4.3 匿名函数

func(params) (returns) {
    ...
}

4.4 闭包

Go 语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。因此,简单的说:

函数 + 引用环境 = 闭包

匿名函数就是一个闭包。

4.5 defer

一般用于释放某些已分配的资源,类似于 Java 的 finally 语句块。

多个延迟执行被注册时,它会逆序执行。

func fileSize(filename string) int64 {
    f, err := os.Open(filename)
    if err != nil {
        return 0
    }
    // 延迟调用 Close, 此时 Close 不会被调用
    defer f.Close()
    info, err := f.Stat()
    if err != nil {
        // defer 机制触发, 调用Close关闭文件
        return 0
    }
    size := info.Size()
    // defer 机制触发, 调用Close关闭文件
    return size
}

4.6 运行时错误

// 自定义简单的错误
var err = errors.New("this is an error")// 一般用 fmt.Errorf

// 自定义错误
type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
    return &MyError{
        time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
        "the file system has gone away",
    }
}

func main() {
    if err := oops(); err != nil {
        fmt.Println(err)
    }
    // Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away
}

4.7 panic 和 recover

panic 机制类似于其他语言的异常,但 panic 的适用场景有一些不同。由于 panic 会引起程序的崩溃,因此 panic 一般用于严重错误,如程序内部的逻辑不一致。

panic() 触发的宕机发生时,panic() 后面的代码将不会被运行,但是在 panic() 函数前面已经运行过的 defer 语句依然会在宕机发生时发生作用

recover 可以让进入宕机流程中的 goroutine 恢复过来。recover 仅在延迟函数 defer 中有效。

Go 没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,那么 recover 的宕机恢复机制就对应 try/catch 机制。

import (
    "fmt"
    "runtime"
)

// 崩溃时需要传递的上下文信息
type panicContext struct {
    function string // 所在函数
}

// 保护方式允许一个函数
func ProtectRun(entry func()) {
    // 延迟处理的函数
    defer func() {
        // 发生宕机时,获取panic传递的上下文并打印
        err := recover()
        switch err.(type) {
        case runtime.Error: // 运行时错误
            fmt.Println("runtime error:", err)
        default: // 非运行时错误
            fmt.Println("error:", err)
        }
    }()
    entry()
}
func main() {
    fmt.Println("运行前")
    // 允许一段手动触发的错误
    ProtectRun(func() {
        fmt.Println("手动宕机前")
        // 使用panic传递上下文
        panic(&panicContext{
            "手动触发panic",
        })
        fmt.Println("手动宕机后")
    })
    // 故意造成空指针访问错误
    ProtectRun(func() {
        fmt.Println("赋值宕机前")
        var a *int
        *a = 1
        fmt.Println("赋值宕机后")
    })
    fmt.Println("运行后")
}

// Output:
// 运行前
// 手动宕机前
// error: &{手动触发panic}
// 赋值宕机前
// runtime error: runtime error: invalid memory address or nil pointer
// dereference
// 运行后

4.8 计算函数执行时间

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    test()
    end := time.Now()
    result := end.Sub(start)
    fmt.Printf("该函数执行完成耗时: %s\n", result)
}

func test() {
    sum := 0
    for i := 0; i < 100000000; i++ {
        sum += i
    }
}

// Output: 该函数执行完成耗时: 32.967898ms

results matching ""

    No results matching ""