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
相关文章推荐
- go语言函数
- Go语言学习笔记---函数
- Go语言中普通函数与方法的区别分析
- Go语言学习笔记(三) [控制结构、内建函数]
- go语言简单的处理http请求的函数实例
- Go语言_函数学习篇
- go语言常用函数
- Go语言中的一些函数
- GO语言学习-内建函数
- go语言函数作为参数传递
- 你猜对了么?一个函数考察Go 语言闭包、defer的用法
- GO语言如何调用C写的函数
- 【go语言】查看包里面的函数
- Go语言中append函数用法分析
- Go语言学习笔记(五) [函数]
- [go语言]函数
- Go语言点滴之函数
- Go语言中普通函数与方法的区别
- Go语言里的new函数用法分析
- Go语言基础入门--函数,错误处理