GO函数

1.1 函数声明

    每个函数声明都包含一个名字,一个形参列表,一个可选的返回列表以及函数体
func fun(x,y  float64) float{
    return 100.00
}

也可以

func fun(x float64,y  float64) float{
    return 100.00
}

1.2函数递归

    函数可以递归调用,这就意味着函数可以直接或者间接的调用自己
    注:许多编程语言都有自己的函数调用栈;大小在64kb到2MB之间。递归的深度受限于固定大小的栈大小。所以进行深度递归时必须注意防止 【栈溢出】。Go语言实现了可变长度的栈,可达到1GB左右的上限,这使得我们可以安全的使用递归而不担心栈溢出问题。

1.3函数多返回值问题

    GO语言和C/C++语言不一样的地方就是,GO语言的函数可以有多个返回值,而c/c++只能有一个返回值。
    注:裸返回:裸返回是将每个命名返回结果按照顺序返回的快捷方法,所以下边的函数中,每个 return 的语句相当于 :return words,images,err
func Country(url string)(words,images int,err error){
    resp, err := http.Get(url)
    if(err != nil){
        return
    }
    doc, err := html.Parse(resp,Body)
    resp.Body.Close()
    if err != nil{
        err = fmt.Errorf("Parse html")
        return
    }
    words, images = countryWords(doc)
    return 
}

1.4 函数错误处理

    当一个函数调用返回一个错误时,调用者应当负责检查并且采取适当的处理应对。根据情景,可能有很多的处理方式。
最常见的处理方式:将错误传递下去

resp, err := http.Get(url)
if err != nil{
return nil, err
}

1.5 函数变量

    函数在 GO语言中是头等重要的值:就像其他值,函数也有类型,而且它们可以赋值给变量或者传递或者从其他函数中返回。函数变量也可以像其他变量一样调用。
 package main

import (
   "fmt"
 )

 func fun() {
     fmt.Println("我是函数")
 }

 func main() {                                                                                                                                                       
     f := fun
     f()
 }
输出结果:我是函数
    函数类型的零值是 nil(空值) ,调用一个空的函数变量将导致宕机。
 package main

  import (
  //"fmt"
 )                                                                                                                                                                   

var f func(int) int

func main() {
     f(3)
 }

运行错误:panic:运行时错误:无效内存地址或nil指针引用。
注:函数变量可以和空值相比较,但是它们本身不能比较。所以不能作为 键值 出现在 map 中。

1.6 匿名函数(函数字面量)

    命名函数只能在包级别的作用域进行声明,但是我们能够使用【函数字面量】在任何表达式内指定函数变量。函数字面量就像函数声明,但是在 func 关键字后面没有函数名称。它是一个表达式,它的值称作匿名函数。
package main

  import (
      "fmt"
 )

 func ret() func() int {
     var x int
     return func() int {
         x++
        return x * x
     }
 }

 func main() {
      f := ret()
      fmt.Println(f())
      fmt.Println(f())
      fmt.Println(f())
      fmt.Println(f())                                                                                                                                                
}

程序输出:1
        4
        9
        16
注:为什么这里的输出是1,4,9,16。因为 ret()函数返回的是一个匿名函数,但是匿名函数使用的 x变量是 ret()函数内部的局部变量。因此匿名函数每调用一次 x++ ,ret()函数内部的 x 就会+1,所以输出的就是1,4,9,16

1.7 变长函数

    变长函数被调用的时候可以有可变的参数个数。最熟悉的函数就是 fmt.Printf() 和它的变种。Printf() 需要在开头提供一个固定的参数,后续便可以接收任意数目的参数。
    在参数列表最后的类型名称之前使用 省略号 "..." 表示声明一个变长函数,调用这个函数的时候可以传递该类型的任意数目的函数。
package main                                                                                                                                                        

 import (
     "fmt"
 )

 func sum(num int, val ...int) int {
     total := 0
     for _, v := range val {
         total += v
     }
     return total
 }

 func main() {
     fmt.Println(sum(1))
     fmt.Println(sum(1))
     fmt.Println(sum(2, 3))
     fmt.Println(sum(1, 2, 3, 4))  
}


程序输出:0
        0
        3
        9


package main                                                                                                                                                        

 import (
     "fmt"
 )

 func sum(val ...int) int {
     total := 0
     for _, v := range val {
         total += v
     }
     return total
 }

 func main() {
     fmt.Println(sum(1))
     fmt.Println(sum(1))
     fmt.Println(sum(2, 3))
     fmt.Println(sum(1, 2, 3, 4))  
}

程序输出:1
        1
        5
        10

1.8 延迟函数调用(defer)

    Go语言中延迟函数defer充当着 cry...catch 的重任,使用起来也非常简便,然而在实际应用中,很多人并没有真正搞明白defer、return和返回值之间的执行顺序,从而掉进坑中,今天我们就来揭开它的神秘面纱!

先来运行下面两段代码:
A. 无名返回值的情况

package main

import (
    "fmt"
)

func main() {
    fmt.Println("return:", a()) // 打印结果为 return: 0
}

func a() int {
    var i int
    defer func() {
        i++
        fmt.Println("defer2:", i) // 打印结果为 defer: 2
    }()
    defer func() {
        i++
        fmt.Println("defer1:", i) // 打印结果为 defer: 1
    }()
    return i
}

程序输出:defer1: 1
        defer2: 2
        return: 0

B. 有名返回值的情况

package main

import (
    "fmt"
)

func main() {
    fmt.Println("return:", b()) // 打印结果为 return: 2
}

func b() (i int) {
    defer func() {
        i++
        fmt.Println("defer2:", i) // 打印结果为 defer: 2
    }()
    defer func() {
        i++
        fmt.Println("defer1:", i) // 打印结果为 defer: 1
    }()
    return i // 或者直接 return 效果相同
}

程序输出结果:defer1: 1
            defer2: 2
            return: 2

先来假设出结论,帮助大家理解原因:
1 多个defer的执行顺序为“后进先出”;

2 defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。


如何解释两种结果的不同:

上面两段代码的返回结果之所以不同,其实从上面第2条结论很好理解。

a()int 函数的返回值没有被提前声名,其值来自于其他变量的赋值,而defer中修改的也是其他变量,而非返回值本身,因此函数退出时返回值并没有被改变。

b()(i int) 函数的返回值被提前声名,也就意味着defer中是可以调用到真实返回值的,因此defer在return赋值返回值 i 之后,再一次地修改了 i 的值,最终函数退出后的返回值才会是defer修改过的值。


C. 下面我们再来看第三个例子,验证上面的结论:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("c return:", *(c())) // 打印结果为 c return: 2
}

func c() *int {
    var i int
    defer func() {
        i++
        fmt.Println("c defer2:", i) // 打印结果为 c defer: 2
    }()
    defer func() {
        i++
        fmt.Println("c defer1:", i) // 打印结果为 c defer: 1
    }()
    return &i
}

程序输出:defer1: 1
        c defer2: 2
        c return: 2

虽然 c()*int 的返回值没有被提前声明,但是由于 c()*int 的返回值是指针变量,那么在return将变量 i 的地址赋给返回值后,defer再次修改了 i 在内存中的实际值,因此函数退出时返回值虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。
即,我们假设的结论是正确的!

1.9 不允许函数内嵌定义

package main

import "fmt"

func main() {
    func swap(x, y string) (string, string) {
    return y, x
    }
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

1.9 宕机

    Go语言的类型系统会捕捉许多编译时错误,但是一些其他错误(数组访问越界等等)都需要在运行时检查。当Go语言运行时检测到这些错误,就会发生宕机。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值