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 多返回值

CC++Java等其它开发语言不同的是是Go语言的函数可以有多个返回值。这个特性能够使得我们写出比其它语言更加优雅和简洁的代码,例如File.Read()函数可以同时返回读取的字节数和错误信息。如果读取成功,返回值n为读取的字节数,errnil,否则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 递归函数

程序调用自身的编程技巧称为递归。递归做为一种算法编程语言中广泛应用。函数调用自身,称为递归函数。

指的是这样一个数列:01123581321、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0F1=1Fn=F(n-1)+F(n-2)n>=2nN*),用文字来说:

² 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(参数)
if err != nil {
    // 错误处理
} else {
    //使用返回值ret
}

内置的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仍然会保证dstFilesrcFile会被正常关闭。一个函数中可以存在多个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 panicrecover函数

Go语言追求简洁优雅,所以不支持传统的 trycatchfinally 这种异常处理结构。Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱,不要用异常代替错误,更不要用来控制流程。在遇到真正的异常的情况下,才使用Go中引入的Exception处理:defer, panic, recoverpanic 是用来表示非常严重的不可恢复的错误的,一般会导致程序挂掉(除非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.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),而被引用的变量所在的父函数已结束。

逃逸的函数内的局部变量一定是堆上分配存储空间的。

原文地址:https://www.cnblogs.com/mazg/p/8275179.html