Go语言-函数

本文介绍了Go语言中的函数定义,包括参数、变参、返回值、命名返回值、匿名函数、延迟调用和错误处理。Go函数支持不定长变参、多返回值和命名返回值,不支持默认参数和重载。文中还讲解了闭包和延迟调用的工作原理,并给出了错误处理的推荐做法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数定义

函数是结构化编程的最小模块单元,使用关键字‘func’定义函数。Go语言定义函数的一些特点总结如下:
- 无需前置声明
- 不支持命名嵌套定义
- 不支持同名函数重载
- 不支持默认参数
- 支持不定长变参
- 支持多返回值
- 支持命名返回值
- 支持匿名函数和闭包
函数属于第一类对象,具备相同签名(参数及返回值类型)的视为同一类型。
定义一个函数:
func test(x int,s string)int{} //其中func为关键字,test为函数名,(x int,s string)为参数列表,int为返回值类型

package main
import(
    "fmt"
)
func hello(){                 //定义一个名为hello的函数,参数列表为空返回值为空
    fmt.Println("hello word!")
}
func main(){                 //main函数程序的入口
    hello()
}

函数只能判断其是否为nil,不支持其它比较操作,从函数返回局部变量指针是安全的。

package main
import(
    "fmt"
)
func test()*int{
    a := 10
    return &a
}
func main(){
    var a *int = test()
    fmt.Println(a,*a)
}

输出:
0xc04203e1d0 10

参数

Go语言不支持有默认值得可选参数,不支持命名实参。调用时,必须按签名顺序传递指定类型和数量的实参,就算以“_”忽略的也不能省略掉。
不管是指针、引用类型,还是其他类型参数,都是值拷贝传递。如果函数参数过多,建议将其定义为一个复合结构类型。

变参

变参本质上就是一个切片。只能接受一个到多个同类型参数,并且放在列表尾部。

package main
import(
    "fmt"
)
func test(s string,a ...int){      //定义一个不定参数的函数,注意'...'不可省略
    fmt.Printf("%s,%T,%v\n",s,a,a) //输出类型及值
}
func main(){
    test("adb",1,2,3,4)
}

输出:
adb,[]int,[1 2 3 4]
既然变参是切片,那么参数复制的仅是切片自身,并不包含底层数组,因此可修改原数据。如果需要可使用内置函数copy复制底层数据。

package main
import(
    "fmt"
)
func test(a ...int){
    for i := range a{
        a[i] += 10
    }
}
func main(){
    a := []int{1,2,3,4}
    test(a...)
    fmt.Println(a)
}

输出:
[11 12 13 14]

返回值

有返回值得函数必须有明确的return语句,除非有panic或者无break的死循环则无需return语句。

package main
import(
    "fmt"
    "errors"
)
func test(x,y int)(int,error){    //函数多返回值
    if 0 == y{
        return 0,errors.New("Can not be zero")
    }
    return x/y ,nil
}
func main(){
    a,ret := test(2,4)
    if nil != ret{
        fmt.Println(ret)
    }else{
        fmt.Println(a)
    }
}

输出:
0

命名返回值

使用命名返回值,可直接使用return隐式返回,如果返回值类型能明确表明其含义,就尽量不要对其命名

package main
import(
    "fmt"
    "errors"
)
func test(x,y int)(a int,err error){   //显示的定义了函数返回值
    if 0 == y{
        err = errors.New("Can not be zero")
        return
    }
    a = x/y
    return 
}
func main(){
    a,ret := test(4,0)
    if nil != ret{
        fmt.Println(ret)
    }else{
        fmt.Println(a)
    }
}

输出:
Can not be zero

匿名函数

匿名函数是指没有定义名字的函数。除没有名字外,匿名函数和普通函数完全相同。最大的区别是我们可在函数内部定义匿名函数,形成类似嵌套效果。匿名函数可直接调用,保存到变量,作为参数或返回值。
直接执行

package main
import(
    "fmt"
)
func main(){
    func(s string){      //匿名函数,无函数名
        fmt.Println(s)
    }("hello word")
}

输出:
hello word
赋值给变量

package main
import(
    "fmt"
)
func main(){
    add := func(x,y int)int{
        return x+y
    }
    fmt.Println(add(1,2))
}

输出:
3
作为参数:

package main
import(
    "fmt"
)
func test(f func()){
    f()
}
func main(){
    test(func(){
        fmt.Println("hello")
    })
}

输出:
hello
作为返回值

package main
import(
    "fmt"
)
func test() func(int,int)int{
    return func(x,y int)int{
        return x+y
    }
}
func main(){
    add := test()
    fmt.Println(add(1,2))
}

输出:
3
普通函数和匿名函数都可以作为结构体字段,或经通道传递。不曾使用的匿名函数会被编译器当做错误。
闭包
闭包(closure)是在其词法上下问引用了自由变量的函数,或者说是函数和其引用的环境组合体。

package main
import(
    "fmt"
)
func test(x int) func(){
    return func(){
        fmt.Println(x)
    }
}
func main(){
    f := test(123)
    f()
}

输出:123
就以上代码而言,test返回的匿名函数会引用上下文环境变量x。当该函数在main中执行时,它依然可正确读取x的值,这种现象就称作闭包。闭包直接引用了原环境变量。
正因为闭包通过指针引用环境变量,那么可能会导致其生命周期延长,甚至被分配到堆内。解决的办法就是每次用不同的环境变量或传参复制,让各自闭包环境各不相同。

延迟调用

语句defer向当前函数注册稍后执行的函数调用。这些函数被称作延迟调用,因为它们直到当前函数函数执行结束前才被执行,常用语资源释放、解除锁定、以及错误处理等操作。

func main(){
    f,err := os.Open("./test.go")
    if err != nil{
        log.Fatalln(err)
    }
    defer f.Close()    //仅注册,直到mian函数退出之前才执行
    ...do something...
}

注意:延迟调用注册的是调用,必须提供执行所需参数(哪怕为空)。参数值在注册时被复制并缓存起来。如对状态敏感,可改用指针或闭包。

package main
import(
    "fmt"
)
func main(){
    x,y := 1,2
    defer func(a int){
        fmt.Println("defer x,y = ",a,y)
    }(x)
    x += 10
    y += 20
    fmt.Println(x,y)
}

输出:
11 22
defer x,y = 1 22
延迟调用可修改当前函数命名返回值,但其自身返回值被抛弃,多个延迟注册按FILO次序(先进后出)执行。

错误处理

error
官方推荐的标准做法是返回error状态
func test(a …interface{})(n int,err error)
标准库将error定义为接口类型,以便实现自定义错误类型。按照惯例,error总是最后一个返回参数。标准库提供了相关创建函数,可方便的创建包含简单错误文本的error对象。

package main
import(
    "fmt"
    "errors"
)
func test(x,y int)(a int,err error){   
    if 0 == y{
        err = errors.New("Can not be zero")//设置了返回错误
        return
    }
    a = x/y
    return 
}
func main(){
    a,ret := test(4,0)
    if nil != ret{
        fmt.Println(ret)
    }else{
        fmt.Println(a)
    }
}

某些时候我们需要自定义错误类型,以容纳更多上下文状态信息。这样做还可以基于类型做出判断

package main
import(
    "fmt"
)
type DivError struct{    //自定义错误类型
    x,y int
}
func (DivError) Error() string{  //实现Error接口方法
    return "division by zero"
}
func div(x,y int)(int,error){
    if y == 0{
        return -1,DivError{x,y}    //返回自定义错误类型
    }
    return x/y,nil
}
func main(){
    z,err := div(5,0)
    if err != nil{
        switch e := err.(type){     //根据错误类型匹配
            case DivError:
                fmt.Println(e,e.x,e.y)
            default:
                fmt.Println(e)
        }
    }
    fmt.Println(z)
}

输出:
division by zero 5 0
-1
panic,recover
与error相比,panic/recover在使用方法更接近try/catch结构化异常。但是它们是内置函数而非语句。panic会立即中断当前函数流程,执行延迟调用。而在延迟调用函数中recover可捕获并返回panic提交的错误对象。

func main(){
    defer func(){
        if err := recover(); err != nil{ //捕获错误
            fmt.Println("erro")
        }
    }()
    panic("dead")                        //引发错误
    fmt.Println("exit")                  //永不会执行
}

因为panic参数是空接口类型,因此可使用任何对象作为错误状态。而recover返回结果同样要做转型才能获得具体信息。
无论是否执行recover,所有延迟调用都会被执行。但中断性错误会沿用堆栈向外传递,要么被外层捕获,要么进程崩溃。连续的调用panic,仅最后一个会被recover捕获。
在延迟函数中panic,不会影响后续延迟调用执行。而recover之后panic,可被再次捕获。另外,recover必须在延迟调用 函数中执行才能正常工作。
建议:除非是不可恢复性、导致系统无法正常工作的错误,否则不建议使用panic

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值