Go语言学习查缺补漏ing Day9

1,333 阅读3分钟

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

Go语言学习查缺补漏ing Day9

本文收录于我的专栏:《让我们一起Golang》

一、又谈defer的执行顺序

请看下面这段代码:

package main
​
import (
    "fmt"
)
​
func f(n int) (r int) {
    defer func() {
        r += n
        recover()
    }()
    var f func()
    defer f()
    f = func() {
        r += 8
    }
    return n + 2
}
func main() {
    fmt.Println(f(3))
}
​

程序运行结果是:

8

为什么呢?执行f(3)时,这段代码会先执行return中的n+2,然后执行defer语句,因为defer语句是先进后出,故先执行defer f(),然后因为此时f()未定义,所以会导致panic异常读书,不过,别急,我们下一个defer语句使用recover()将异常回收了,然后r += n,也就是r加3,因为r+8未执行,所以和是3+2+3=8.

二、切片底层数组的一个有关问题

再看下面这段代码:

package main
​
import (
    "fmt"
)
​
func change(s ...int) {
    s = append(s, 8)
}
func main() {
    slice := make([]int, 5, 5)
    slice[0] = 0
    slice[1] = 1
    fmt.Println(slice)
    change(slice...)
    fmt.Println(slice)
    change(slice[0:2]...)
    fmt.Println(slice)
    change(slice[0:3]...)
    fmt.Println(slice)
    change(slice[0:4]...)
    fmt.Println(slice)
    change(slice[0:5]...)
    fmt.Println(slice)
}
​

你认为输出结果是什么?

下面是输出结果。你答对了吗?

[0 1 0 0 0]
[0 1 0 0 0]
[0 1 8 0 0]
[0 1 8 8 0]
[0 1 8 8 8]
[0 1 8 8 8]

答对的小伙伴可以看下一点啦~

答错的小伙伴来听我解释一下吧~

如果函数change(s ...int)的参数类型使用...,可以将slice切片传入而不会新建切片。这里第一次调用change()函数前,创建的切片容量和长度是相等的,所以调用change()函数后,切片进行扩容,会导致生成新的切片,8也加在新的切片上,原来的切片不会发生变化。

change(slice[0:2]...)
fmt.Println(slice)

这行代码使用[0:2]截取生成了一个新的切片,但是它的底层数组与之前的切片的底层数组是一样的,不过新切片的长度是2,而之前切片的长度是5.所以在这个切片上append元素我们打印旧切片slice能够看到,也就是:

[0 1 8 0 0]

然后我们一直截取一直增加,直到:

change(slice[0:5]...)
fmt.Println(slice)

此时切片的容量和长度已经加到一致了,再append来添加元素就会导致扩容,而导致底层数组变化。所以再次打印slice切片,就会还是:

[0 1 8 8 8]

三、for...range 遍历数组与切片的区别

package main
​
import (
    "fmt"
)
​
func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int
    for i, v := range a {
        if i == 0 {
            a[1] = 8
            a[2] = 8
        }
        r[i] = v
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}

先来看一看运行结果:

r =  [1 2 3 4 5]
a =  [1 8 8 4 5]

我们可以看到,遍历时保存的r数组内的值与最终输出的数组不一致,我们前面的文章说过,这是因为for...range遍历数组时,会生成一个副本,然后遍历这个副本,所以我们在循环中修改数组的值并不会修改副本的值,所以导致两个数组结果不一致。

而切片就不一样了,我们来看一看切片会有什么运行结果:

package main
​
import (
    "fmt"
)
​
func main() {
    var a = []int{1, 2, 3, 4, 5}
    var r [5]int
    for i, v := range a {
        if i == 0 {
            a[1] = 8
            a[2] = 8
        }
        r[i] = v
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}
​

这是运行结果:

r =  [1 8 8 4 5]
a =  [1 8 8 4 5]

我们可以看到两个是相同的,这是因为虽然是副本,但是指向的是同一个底层数组,因此修改副本的底层数组也会修改原切片的底层数组。