您的位置:首页 > 编程语言 > Go语言

Go语言-函数

2016-11-20 20:04 281 查看

函数定义

函数是结构化编程的最小模块单元,使用关键字‘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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: