函数(二)

函数(二)

5.7 可变参数

Go语言支持可变参数特性,函数声明和调用时没有固定数量的参数,同时也提供了一套方法进行可变参数的多级传递。

Go语言的可变参数格式如下:

func 函数名(固定参数列表,v…T)(返回参数列表){

函数体

}

特性如下:

  • 可变参数一般被放置在函数列表末尾,前面是固定参数列表,若没有固定参数,所有变量就将是可变参数。

  • v为可变参数,类型为[]T,v和T之间由“…”三个点组成。

  • T为可变参数的类型,当T为interface{}时,传入的可以是任意类型。

5.7.1 fmt包中的例子

可变参数有两种形式:所有参数都是可变参数的形式,如fmt.Println,以及部分是可变参数的形式,如fmt.Printf,可变参数只能出现在参数的后半部分 ,不可变参数只能放在前面。

  1. 所有参数都是可变参数:fmt.Println

    fmt.Println的函数声明如下:

    func Println(a …interface{}) (n int , e error) {

    return Fprintln(os.Stdout, a…)

    }

  2. 部分参数是可变参数:fmt.Printf

    fmt.Printf 的第一个参数为参数列表,后面的是可变参数,fmt.Printf函数的格式如下:

    func Printf(format string, a…interface{}) (n int, e error) {

    return Fprintln(os.Stdout, format, a…)

    }

    fmt.Printf()函数在调用 时, 个函数始终必须传入字符串 ,对应参数是 format,后面的参数数量可以变化,使用时,代码如下:

    fmt . Printf (” pure string\n”)

    fmt . Printf (” value f\n ”, true math .Pi)

5.7.2 遍历可变参数列表

如果需要获得每个参数的具体值,可以对可变参数遍历进行遍历,参见如下代码:

func joinstring(slist ...string) string {
    var b bytes.Buffer
    for _, s := range slist {    //遍历可变参数列表slist,类型是[]string
        b.WriteString(s)
    }
    return b.String()    //将连接好的字节数组转换为字符串
}
func main() {
    fmt.Println(joinstring("pig ", "and", " cat"))
}

5.7.3 获得可变参数的类型

可变参数为interface{}类型时,可以传入任何类型的值。此时,需要获取变量的类型,可以通过switch类型分支获得变量的类型,如下代码所示:

func printTypeVal(slist ...interface{}) string {
    var b bytes.Buffer
    for _, s := range slist {
        str := fmt.Sprintf("%v", s)    //
        var typeString string    //类型的字符串描述
        switch s.(type) {    //对s进行类型断言
        case bool:
            typeString = "bool"
        case int:
            typeString = "int"
        case string:
            typeString = "string"
        }
        b.WriteString("value: ")
        b.WriteString(str)
        b.WriteString("type: ")
        b.WriteString(typeString)
        b.WriteString("\n")
    }
    return b.String()
}

5.7.4 在多个可变参数函数中传递参数

可变参数是一个包含所有参数的切片,如果要在多个可变参数函数中传递参数,可以在传递时在可变参数的变量中默认添加“…”,将切片的元素进行传递。

5.8 延迟执行语句

Go语言的defer语句会将其后跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行

5.8.1 多个延迟执行语句的处理顺序

参见演示代码:

func main() {
    fmt.Println("defer begin")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("defer end")
}

/*
代码输入如下:
defer begin
defer end
3
2
1
*/

5.8.2 使用延迟语句在函数退出时释放资源

处理业务或逻辑中涉及成对的操作是一件比较繁琐的事情,比如打开和关闭文件,接收和回复请求、加锁和解锁等。这些操作中,最容易忽略的就是在每个函数退出处正确的释放和关闭资源。

defer的语句正好是在函数退出时执行,所以用defer能方便的处理资源释放问题。

  1. 使用延迟并发解锁

    在下面例子中函数中并发使用map,为防止竞态问题,使用sync.Mutex进行加锁,参见下面代码:

    var (
        valueBykey = make(map[string]int)
        vbGuard sync.Mutex
    )
    func readValue(key string) int {
        vbGuard.Lock()
        defer vbGuard.Unlock()   //defer语句添加解锁,等readValue()返回时才执行
        return valueBykey[key]
    }
    
  2. 使用延迟释放文件句柄

    在下面的例子中将实现根据文件名获取文件大小的函数,函数中需要打开文件、获取文件大小和关闭文件等操作。由于每一步系统操作都需要进行错误处理,而每一步处理都会造成一次可能的退出,因此就需要在退出时释放资源,而我们需要密切关注在函数退出处正确地释放文件资源。参考下面的代码:

func filesize(filename string) int64{
    f, err := os.Open(filename)
    if err != nil {
        return 0
    }
    defer f.Close()    //延迟调用Close()
    if info, err := f.Stat(); err != nil {
        return 0    
    } else {
        size := info.Size()
        return size
    }
}

5.9 处理运行时发生错误

Go语言的错误处理思想及设计包含以下特征:

  • 一个可能造成错误的函数,需要返回值中返回一个错误接口(error)。如果调用是成功的,错误接口将返回nil,否则返回错误

  • 在函数调用后需要检查错误,如果发生错误,进行必要的错误处理。

5.9.1 错误接口的定义形式

error是Go系统声明的接口类型,代码如下:

type error interface {

    Error() string

}

所有符合Error() string格式的方法,都能实现错误接口。

Error()方法返回具体的错误描述,使用者可以通过这个字符串知道发生了什么错误。

5.9.2 自定义一个错误

返回错误前,需要定义会产生那些可能的错误。在Go语言中,使用errors包进行错误的定义,格式如下:

var error = errors.New("this is a new error")

错误字符串相对固定,一般在包作用域声明,应尽量减少在使用时直接使用errors.New返回。

在代码中使用错误定义,示例如下:

package main

import(
    "errors"
    "fmt"
)
//定义除数为0的错误
var errDivisionByZero = errors.New("division by zeor")


func div(dividend, divisor int) (int, error) {
    if divisor == 0 {    //如除数为0
        return 0, errDivisionByZero
    }
    return dividend/divisor, nil    //正常计算,空错误
}
func main() {
    fmt.Println(div(1,0))
}

5.10 宕机(panic)

宕机不是一件好事,可能造成体验停止、服务中断。但是,如果在损失发生时,程序没有因宕机而停止,则付出更大代价。因此,宕机有时是一种合理的止损方法。

5.10.1 手动触发宕机

Go语言可以在程序中手动触发宕机,让程序崩溃,让开发者及时发现错误,同时能减少可能的损失。

Go语言程序在宕机时,会将堆栈和goroutine信息输出到控制台,所以宕机也可以方便地知晓发生错误的位置。

代码中用一个内建的函数panic()就可以造成崩溃,panic()的声明如下:

func panic(v interface{})

panic()的参数可以是任意类型,后文提到的recover参数会接收从panic()中发出的内容。

5.10.2 在运行依赖必备的资源缺失时,主动触发宕机

regexp是Go语言的正则表达式包,正则表达式需要编译后才可以使用,且必须编译成功,表示正则表达式可用。

编译正则表达式函数有两种,具体如下:

  1. func Compile(expr string) (*Regexp, error)

编译正则表达式 发生错误时返回编译错误, Rege均为 nil 该函数适用于在编译错误时获得编译错误进行处理,同时继续后续执行的环境。

  1. func MustCompile(str string) *Regexp

当编译正则表达式发生错误时,使用 panic触发宕机,该函数适用于直接使用正则表达式而无须处理正则表达式错误的情况。

5.10.3 在宕机时触发延迟执行语句

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

package main

import "fmt"

func main() {
    defer fmt.Println("宕机后要做的事1")
    defer fmt.Println("宕机后要做的事2")
    panic("宕机")
}
/*
输出如下:
宕机后要做的事2
宕机后要做的事1
宕机
*/

5.11 宕机恢复(recover)

无论是代码运行错误由Runtime层抛出的panic崩溃,还是主动触发的panic崩溃,都可以配合defer和recover实现错误捕捉和恢复,让代码在发生崩溃后允许继续允许。

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

5.11.1 让程序在崩溃时继续运行

下面的代码实现了 ProtectRun()函数,该函数传入 个匿名函数或闭包后的执行函数,当传入函数 以任何形式发生 panic 崩溃后,可以将崩溃发生的错误打印出来,同时允许后面的代码继续运行,不会造成整个进程的崩溃。

package main


import (
    "fmt"
    "runtime"
)
//崩溃时需要传递的上下文信息
type panicContext struct {
        function string
}
func ProtectRun()(entry func()) {
    defer func() {
        err := recover()    //发生宕机时,获取panic传递的上下文打印
        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(&panicContext{
            "手动触发panic",
        })
        fmt.Println("手动宕机后")
    })
    //故意造成空指针访问错误
    ProtectRun(func(){
        fmt.Println("赋值宕机前")
        var a *int
        *a = 1
        fmt.Println("赋值宕机后")
    })
    fmt.Println("运行后")
}

5.11.2 panic和recover的关系

panic和recover的组合有以下几个特性

  • 有panic没有recover,程序宕机

  • 有panic也要recover捕获,程序不会宕机。执行完对应的defer后,从宕机点退出当前函数后继续执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值