面试题

最近Go 101的作者公布了11道Go面试题,十分乏味,打算写一个系列对每道题做具体解析,欢送大家关注。

本题是Go quiz slice系列的第2道题目,这道题十分有迷惑性。

通过这道题咱们能够通晓对slice做range遍历的坑,防止在理论我的项目中踩坑。

package mainfunc main() {    var x = []string{"A", "B", "C"}    for i, s := range x {        print(i, s, ",")        x[i+1] = "M"        x = append(x, "Z")        x[i+1] = "Z"    }}
  • 0A,1B,2C,
  • 0A,1Z,2Z,
  • 0A,1M,2M,
  • 0A,1M,2C,
  • 0A,1Z,2M,
  • 0A,1M,2Z,
  • (infinite loop)

大家能够在评论区留下你们的答案。这道题次要有以下几个考点:

  1. slice做range遍历,Go编译器背地会做哪些事件?
  2. slice什么时候扩容,扩容后的行为是怎么样的?

解析

咱们先一一解答下面的问题。

range遍历机制

range对slice做遍历的时候,实际上是先结构一个原slice的拷贝,再对这个拷贝做遍历。

在for循环外面的逻辑执行之前,这个拷贝的值就确定下来了。因而这个拷贝的长度和容量是不会在for循环的时候产生扭转的。

以下面的题目为例:range x 实际上是会先结构一个原切片x的拷贝,咱们假如为y,而后对y做遍历。

for i, s := range x {        print(i, s, ",")        x[i+1] = "M"        x = append(x, "Z")        x[i+1] = "Z"}

下面这段代码能够等价为:

y := xfor i := 0; i < len(y); i++ {    print(i, y[i], ",")    x[i+1] = "M"    x = append(x, "Z")    x[i+1] = "Z"}

slice扩容机制

通过append函数给slice增加元素的时候,有2种状况:

  • 如果切片的容量足够,就会在切片指向的底层数组里追加元素。
  • 如果切片的容量不足以承载新增加的元素,就会开拓一个新的底层数组,把原切片里的元素拷贝过去,再追加新的元素。切片构造里的指针会指向新的底层数组。

答案

咱们回到本文最开始的题目,逐行解析每行代码的执行后果。

代码程序执行后果
var x = []string{"A", "B", "C"}x是一个切片,长度是3,容量是3,x指向的底层数组的值是[ "A" "B" "C"]
for i, s := range x编译器先结构一个切片x的拷贝,假如为切片y,而后对y做遍历。y的值在for循环执行前就确定下来了,长度为3,容量为3,固定不变。
print(i, s, ",")第一次for循环,i的值是0,s的值是切片y里下标索引为0的元素,值为"A",打印0A
x[i+1] = "M"执行x[1] = "M",因为切片xy当初指向同一个底层数组,切片y里下标索引为1的元素的值也被改成了"M",y指向的底层数组的值为["A", "M", "C"]
x = append(x, "Z")给切片x增加新元素"Z",因为以后切片x的长度为3,容量为3,容量已满,不足以承载新减少的元素,所以要对x的底层数组做扩容,x指向新的底层数组,新底层数组的值是["A", "M", "C", "Z"],y还是指向原来的底层数组,y指向的底层数组的值是["A", "M", "C"]
后续for循环因为从第2次for循环开始,xy指向了不同的底层数组,所以对切片x的批改不会影响到y,因而前面打印的后果顺次是1M,2C

所以本题的答案是 0A, 1M, 2C。

总结

对于slice,时刻想着对slice做了批改后,slice里的3个字段:指针,长度,容量是怎么变的。

zen of go

  • 对切片x做range遍历,实际上是对x的拷贝(假如为y)做range遍历,y的值(包含y构造体里指向底层数组的指针的值,y的长度和容量)都在执行for循环前确定下来了。
  • 切片的底层数据结构和扩容机制,如果有不分明的,参考我写的slice底层原理篇,蕴含了slice的所有注意事项。

开源地址

文章和示例代码开源地址在GitHub: https://github.com/jincheng9/...

公众号:coding进阶

集体网站:https://jincheng9.github.io/

思考题

留下2道思考题,欢送大家在评论区留下你们的答案。也能够在我的wx公号发送音讯slice2获取答案和起因。

  • 题目1:

    package mainfunc main() {    var x = []string{"A", "B", "C"}    for i, s := range x {        print(i, s, " ")        x = append(x, "Z")        x[i+1] = "Z"    }}
  • 题目2

    package mainfunc main() {    var y = []string{"A", "B", "C", "D"}    var x = y[:3]    for i, s := range x {        print(i, s, ",")        x = append(x, "Z")        x[i+1] = "Z"    }}

References

  • https://go101.org/quizzes/sli...
  • https://mp.weixin.qq.com/s?__...
  • https://jincheng9.github.io/p...