Go语言 5 函数
文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/
今天,我们来学习Go语言编程的第五章,函数。首先简单说一下函数的概念和作用。函数是一系列语句的集合。一般是为了完成某一特定功能而定义的。这样在需要使用该功能时,直接调用该函数即可,而不用再去写一堆代码,实现了代码的复用。另外,在需要修改该功能时,也只需修改这一个函数即可,方便了代码的维护。
5.1 函数定义
函数定义包括函数名、形参列表、返回值列表以及函数体。一般语法格式如下:
func 函数名称( [形参列表] ) [返回值列表]{ 函数体 } |
示例:
func Max(a int, b int) int { if a > b { return a } return b } func main() {
max := Max(10, 5)//调用函数,结果10 fmt.Println(max) max = Max(-10, 5)//调用函数,结果5 fmt.Println(max) } |
Go函数中的参数没有默认参数值。形参和返回值的变量名对于函数调用者而言没有意义。
5.2 可变参数
参数数量可变的函数称为可变参数的函数。在定义可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“...”,表示该函数可接收任意数量的该类型参数。
func Sum(vals ...int) int { total := 0 for _, v := range vals { total += v } return total } |
func main() {
fmt.Println(Sum(1, 2)) //2个参数 fmt.Println(Sum(1, 2, 3)) //3个参数 slice := []int{1, 2, 3} //把slice作为参数,需要打散 fmt.Println(Sum(slice...)) } |
5.3 多返回值
与C、C++和Java等其它开发语言不同的是是Go语言的函数可以有多个返回值。这个特性能够使得我们写出比其它语言更加优雅和简洁的代码,例如File.Read()函数可以同时返回读取的字节数和错误信息。如果读取成功,返回值n为读取的字节数,err为nil,否则err为具体的错误信息。
func (file *File) Read(b []Byte)(n int,err Error) |
一个简单的多返回值示例:
// 多个返回值,另外同类型的参数列表或返回值列表可以采用以下简写形式 func AddAndSub(a, b int) (add, sub int) { return a + b, a - b } func main() {
v1, v2 := AddAndSub(6, 3) fmt.Println(v1, v2) // 9,3 } |
5.4 递归函数
程序调用自身的编程技巧称为递归。递归做为一种算法在编程语言中广泛应用。函数调用自身,称为递归函数。
指的是这样一个数列:0、1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*),用文字来说:
² 前2个数是 0 和 1 。
² 第 i 个数是第 i-1 个数和第i-2 个数的和。
// 递归实现, func Fib(n int) int { if n < 2 { return n } return Fib(n-1) + Fib(n-2) } func main() { for i := 0; i < 10; i++ { fmt.Print(Fib(i), " ") } } |
5.5 错误处理
对于大多数函数而言,无法确保函数一定能够成功运行,因为产生错误的原因很有可能超出程序员控制范围。例如,任何I/O操作的函数都有出现错误的可能。如果函数需要返回错误,通常将error作为多种返回值中的最后一个,虽然这并非是强制要求。
一般为如下模式:
func Foo(参数列表)(ret list, err error) { |
调用时的处理模式:
n, err := Foo(参数) |
内置的error是接口类型。
常用的处理错误的5种方式:
1传递错误
2 如果错误是偶然的,重新尝试失败的操作。
3 输出错误信息,并结束程序,一般在main()函数中使用。
4 输出错误信息,并且不中断程序
5 直接忽略错误
5.6 defer
当一个函数调用前有关键字 defer 时, 那么这个函数的执行会推迟到包含这个defer 语句的函数即将返回前才执行。
func main() {
defer fmt.Println(3) fmt.Println(1) fmt.Println(2) } //打印结果: 1 2 3 |
defer通常用于 open/close, connect/disconnect, lock/unlock 等这些成对的操作, 来保证在任何情况下资源都被正确释放。下面是一个文件拷贝的例子:
func CopyFile(dst, src string) (w int64, err error) { srcFile, err := os.Open(src) if err != nil { return } defer srcFile.Close() dstFile, err := os.Create(dstName) if err != nil { return } defer dstFile.Close() return io.Copy(dstFile, srcFile) } |
即使其中的Copy()函数抛出异常, Go仍然会保证dstFile和srcFile会被正常关闭。一个函数中可以存在多个defer语句, defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行。
defer也可以跟一个用户自定义的匿名函数。
defer func() { // 匿名函数的代码 } () |
defer 调用的函数参数的值在defer被定义时就确定了,而 defer 函数内部所使用的变量的值是在这个函数运行时才确定。
func main() { i := 1 defer fmt.Println("延时打印:", i) //i是参数 i++ fmt.Println("常规打印:", i) } //打印结果: 常规打印: 2 延时打印: 1 func main() { i := 1 defer func() { fmt.Println("延时打印", i) //i是内部变量 }() i++ fmt.Println("常规打印:", i) } //打印结果: 常规打印: 2 延时打印 2 |
defer 函数调用的执行时机是外层函数设置返回值之后, 并且在即将返回之前。也就是说“return 返回值”语句并不是原子的。请看下面的例子:
func double(x int) int { return x + x } func triple(x int) (r int) { defer func() { r += x }() return double(x) } func main() { fmt.Println(triple(3)) } 打印结果: 9 // 等价与下面代码,打印结果是9 func triple(x int) (r int) { // 1 设置返回值 r = double(x) // 2 执行defer语句的函数 func() { r += x }() // 3 函数返回 return } |
5.7 panic、recover函数
Go语言追求简洁优雅,所以不支持传统的 try…catch…finally 这种异常处理结构。Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱,不要用异常代替错误,更不要用来控制流程。在遇到真正的异常的情况下,才使用Go中引入的Exception处理:defer, panic, recover。panic 是用来表示非常严重的不可恢复的错误的,一般会导致程序挂掉(除非recover)。Go语言对异常的处理可以这样简单描述:Go程序的执行过程中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常。
func main() { defer func() { // 必须要先声明defer,否则不能捕获到panic异常 fmt.Println("c") if err := recover(); err != nil { fmt.Println(err) // 这里的err其实就是panic传入的内容 } fmt.Println("d") }() foo() } func foo() {
fmt.Println("a") panic("产生异常") fmt.Println("b") fmt.Println("f") } //打印结果: a c 产生异常 d |
5.8 函数值
在Go中,函数被看作第一类值。函数值像其它值一样,拥有类型。函数类型可以使用type关键字定义,通过函数的参数和返回值区分。
函数值可以被赋值给其它变量,也可以作为其他函数的参数或返回值。对函数值的调用相当于对函数的调用。函数值属于引用类型,两个函数值之间不可比较。
package main
import "fmt"
type handle func(int) int
func main() {
//1给函数变量赋值 v := func(n int) int { return n * n } //2 通过函数值调用函数 fmt.Println(v(3)) var h handle h = v fmt.Println(h(3)) } |
5.9 匿名函数与闭包
1 匿名函数
顾名思义,匿名函数就是定义时没有函数名称的函数。
func main() { add := func(x, y int) int { //匿名函数赋值给变量add return x + y } fmt.Println(add(10, 5)) //通过变量调用函数 func(x, y int) { fmt.Println(x - y) }(10, 5) //定义时直接调用 } |
2 闭包
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块为自由变量提供绑定的计算环境(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)。
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
package main
import "fmt"
func Sqrt(num int) func() int { //变量num与匿名函数在同一环境中定义 return func() int { num++ return num * num } } func main() { s := Sqrt(0) //外部函数执行完成后,num并没有被销毁,值为0; fmt.Println(s()) //执行s函数,num的值为1,函数的返回值为1 fmt.Println(s())//执行s函数,num的值为2,函数的返回值为4 fmt.Println(s())//执行s函数,num的值为3,函数的返回值为9 } //打印结果:1 4 9 |
闭包函数出现的条件:
1.被嵌套的函数引用到非本函数的外部变量,而且这外部变量不是“全局变量”
2.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),而被引用的变量所在的父函数已结束。
逃逸的函数内的局部变量一定是堆上分配存储空间的。