zoukankan      html  css  js  c++  java
  • 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.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),而被引用的变量所在的父函数已结束。

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

  • 相关阅读:
    通俗算法教程04
    微软是如何重写C#编译器并使它开源的
    在 CentOS 7 中安装 MySQL 8
    在 .NET Core 中结合 HttpClientFactory 使用 Polly(下篇)
    在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)
    .NET 开源项目 Polly 介绍
    在 .NET Core 中结合 HttpClientFactory 使用 Polly(上篇)
    5年后、10年后,你希望自己是个什么样的人?
    即将发布的 ASP.NET Core 2.2 会有哪些新玩意儿?
    推荐六本前端开发必看的书籍
  • 原文地址:https://www.cnblogs.com/mazg/p/8275179.html
Copyright © 2011-2022 走看看