Golang教程第6篇-内置函数、递归函数、闭包

1.内置函数

在Go里面有一些内置函数不需要通过包的调用直接就可以使用,比如前面学习中的len、append等,下面分别通过例子来学习这些内置函数的基本使用方法

1.1.append

append用来追加元素到数组,看下面几个例子,为了方便展示,下面都是以slice为例

package main

import (
    "fmt"
)

func main() {
    var a []int
    a = append(a,1,2,3)
    fmt.Println(a)
}

编译执行
05go49

package main

import (
    "fmt"
)

func main() {
    var a []int
    a = append(a,1,2,3)
    a = append(a,a...)
    fmt.Println(a)

编译执行
05go50

package main

func Test(a []int) {
    a = append(a,1,2,3)
    fmt.Println(a)
}

func main() {
    var a []int
    Test(a)
}

编译执行
05go51

以上三个例子分别是在一个空slice基础上追加元素,其中第二个例子追加了两次,第二次直接追加的slice,用a...表示

再来看三个例子

package main

import (
    "fmt"
)

func main() {
    var i []byte
    i = append(i,'a','b','c')
    fmt.Printf("%c",i)
}

编译执行
05go52
这里说明一下如果是不使用%c进行格式化输出,那么输出的是字符的ascii码

package main

import (
    "fmt"
)

func main() {
    var i []byte
    i = append(i,'a','b','c')
    i = append(i,i...)
    fmt.Printf("%c",i)
}

编译执行
05go53

package main

import (
    "fmt"
)

func Test(i []byte) {
    i = append(i,'a','b','c')
    fmt.Printf("%c",i)
}

func main() {
    var i []byte
    Test(i)
}

编译执行
05go54

1.2.new

new用来分配内存,主要用来分配值类型,如int struct,返回一个地址(指针)

package main

import (
    "fmt"
)

func main() {
    var i int
    fmt.Println(i)
    j := new(int)
    fmt.Println(j)
}

编译执行
05go55
new里面传递的参数必须是类型

1.3.make

make用来给引用类型分配内存,如chan、slice、map

package main

import (
    "fmt"
)

func main() {
    b := make(chan int,1)
    fmt.Println(b)
}

编译执行
05go56

1.4.panic与recover

panic与recover用来做错误处理,有时候程序运行不下去会抛出错误,但如果这个错误不影响接下来的程序,可以使用recover来捕获错误

package main

import (
    "fmt"
)

func Test() {
    var b int = 0
    a := 100 / b
    fmt.Println(a)
    return
}

func main() {
    Test()
}

编译执行
05go60
从图中可以看到程序panic后报错退出了,如果不想程序就此退出,可以利用recover来捕获此错误

package main

import (
    "fmt"
    "runtime/debug"
)

func Test() {

    defer func() {
        if err := recover();err != nil {
            fmt.Println(err)
            debug.PrintStack()
        }
    }() 
    
    var b int = 0
    a := 100 / b
    fmt.Println(a)
    return
}

func main() {
    Test()
    time.Sleep(time.second)
}

编译执行
05go61
通过调用匿名函数使用recover来捕获panic,打印出错误和错误堆栈,同时为了说明程序没有退出这一点,在main函数中调用Test函数时使用无限循环,图中持续输出的错误也表明程序一直再运行

panic的这种特性在实际案例中非常有用,比如该错误只影响本次请求,可以使用,再比如判断某个配置初始化是否成功,可以自定义一个panic

package main

import (
    "fmt"
    "errors"
    "time"
)

func Initconfig() (err error) {
    return errors.New("init config failed")
}

func Test() {
    defer func() {
        err := recover();err != nil {
            fmt.Print(err)
            debug.Printack()
        }
    }
    
    err := Initconfig()
    if err != nil {
        panic(err)
    }
}

func main() {
   for {
       Test()
       time.Sleep(time.Second)
   }   
}

编译执行
05go62
代码中是直接输出了错误文本来赋值给err变量,然后通过panic来获取err,最后recover来捕获,现实里肯定是需要对函数Initconfig返回错误的文本进行判断的,这里只是为了方便说明如何自定义panic

1.5.new和make的区别

前面的例子发现new和make都返回了一个地址的例子,这里说一下两者之间的区别:new时返回一个指针,肯定是一个地址,但是make时返回一个值,前面有个对chan类型的make例子,这是因为chan本身值就是地址,看下面这个例子就清楚了

package main

import (
    "fmt
)

func main() {
    s1 := new([]int)
    fmt.Println(s1)
    
    s2 := make([]int,5)
    fmt.Println(s2)
}

编译执行
05go63
很明显一个返回的是地址,一个是值

package main

import (
    "fmt"
)

func main() {
    s1 := new([]int)
    s2 := make([]int,5)
    (*s1)[0] = 100
    s2[0] = 100
}

编译执行
05go64
报错了,这是因为是s1是一个指针,*是指向这个指针绑定的内存地址指向的值,但是这个值是空的,如果要使用需要变量初始化,这个不同于值类型,比如int,它默认就是0,而slice默认是null,是无法进行值传递的,作如下修改

package main

import (
    "fmt"
)

func main() {
    s1 := new([]int)
    *s1 = make([]int,5)
    (*s1)[0] = 100
    fmt.Println(s1
}   

编译执行
05go65
图中显示分别输出了地址和地址指向的值

2.递归函数

在Go里一个函数自己调用自己叫做递归

package main 

import (
    "fmt"
    "time"
)

func Test(n int) {
    fmt.Println("hello")
    time.Sleep(time.second)
    if n > 5 {
        return
    }
    Test(n + 1)
}

func main() {
    Test(0)
}  

编译执行
05go66

再来看一个递归的例子

package main

import (
    "fmt"
)

func Calc(n int) int {
    if n == 1 {
        return 1
    }
    return Calc(n - 1) *n
}

func main() {
    result := Calc(5)
    fmt.Println(result)
}

编译执行
05go67

斐波拉契数也可以利用递归来实现

我自己写的

package main

import (
    "fmt"
)

func Process(i int) {
    for a := 0;a < i;i++ {
        result := Test(a)
        fmt.Printf("%d\t",resullt)
    }
}

func Test(a int) (result int) {
    if a <= 1 {
        result = 1
        return 
    }
    result = Test(a - 1) + Test(a - 2)
    return
}

func main() {
    Process(10)
}

编译执行
05go68

老师写的

package main

import (
    "fmt"
)

func Test(i int) int {
    if i <= 1 {
        result = 1
        return result
    }
    return Test(i - 1) + Test(i - 2)
}

func main() {
    for i := 0;i < 10;i++ {
        result := Test(i)
        fmt.Printf("%d\t",result)
    }
}

如果某个问题可以分解成相似的小问题,可以尝试使用递归来解决,当然需要设定好出口条件

比如常见1+。。+100也可以利用递归来计算

package main

import (
    "fmt"
)

func Test(i int) (result int) {
    if i == 0 {
        return
    }
    result = Test(i - 1) + i
    return
}

func main() {
    sum := Test(100)
    fmt.Printf("%d\n",sum)
}

编译执行
05go69

3.闭包

闭包指的是一个函数和其相关的引用环境组合的实体,下面是两个闭包的例子

package main

import (
    "fmt"
)

func Adder() func(int) int {
    var x int
    return func(d int) int {
        x += d
        return x
    }
}

func main() {
    f := Adder()
    fmt.Println(f(1))
    fmt.Println(f(100))
    fmt.Println(f(1000))
}

编译执行
05go70
上面的代码运行过程如下:main函数中将函数Adder赋值给变量f;f(1)表示调用函数Adder并传参一个数值1到闭包里的函数参数中d上,计算x并返回x值为1,x等于1与闭包函数绑定了,下面调用f(100)进行计算的时候x是等于1而不是等于0,返回时x计算的值等于101,接着调用f(100)时返回x值等于1101

package main

import (
    "fmt"
    "strings"
)


func MakeSuffix(suffix string) func(string) string {
    return func(name string) string {
        if strings.HasSuffix(name,suffix) == false {
            return name + suffix
        }
        return name
    }
}

func main() {
    func1 := MakeSuffix(".jpg")
    func2 := MakeSuffix(".png")
    fmt.Println(func1("test"))
    fmt.Println(func2("test"))
    fmt.Println(func1("apple"))
}

编译执行
05go71
运行过程:将函数MakeSuffix(".jpg")赋值给变量func1,func1("test")调用并传递一个字符串参数test到func(name string) string {}中,判断name(本例中为test)是否以suffix(本例为.jpg)
结尾,不是就返回name+suffix,本例中闭包函数与suffix绑定