Go 语言入门指南——切片的创建与使用 | 豆包MarsCode AI 刷题

182 阅读4分钟

#切片

1. 切片的创建和数组创建的区别

切片的创建不用指定数值大小。 创建方式:

  • var或者:=
  • make方式
  1. var或者:=初始化的方式:
    • 通过初始化切片变量,然后使用append函数往切片中添加元素。
package main  
  
import "fmt"  
  
func main() {  
    var slice []int  // 创建切片类型
    slice = append(slice, 1)  
    slice = append(slice, 2)  
    slice = append(slice, 3)  
    slice = append(slice, 4)  
    slice = append(slice, 5)  
    fmt.Println(slice, len(slice), cap(slice))  
  
}
/*
[1 2 3 4 5] 5 8
*/
  • 问题:为什么最后打印出来的切片的容量为8?
    • 提示:好像是跟append有关系。
    • 解答参见文章第6节:6. 对切片进行动态扩容为什么会改变底层原数组
  1. make函数的方式
    • 第二个参数为len:切片元素长度。
    • 第三个参数为cap:切片容量,也就是底层数组的大小。
package main  
  
import "fmt"  
  
func main() {  
    slice1 := make([]int, 5)    //  指定len为5,cap容量默认也为5  
    slice2 := make([]int, 5, 8) // 指定len为5,cap为8  
    slice1[0] = 11  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
}

2. 切片类型如果不初始化,声明后直接使用会怎么样?

如果不初始化,切片变量就没有分配内存空间,容量为0。此时的切片变量等于nil空。 如果不初始化直接使用,会报数组访问越界的panic。

3. 如何避免切片未初始化的风险?

检查切片变量是否为空,为空则进行初始化。 两种方式:

  1. 直接make初始化。
  2. 使用append添加元素,会对切片进行扩容初始化
func main() {  
    var slice1 []int  
    fmt.Println(cap(slice1))  
    fmt.Println(slice1 == nil)  
    if slice1 == nil {  
       slice1 = make([]int, 10)  
    }  
    slice1[0] = 19  
    fmt.Println(slice1)  
}
/*
0
true
[19 0 0 0 0 0 0 0 0 0]
*/

4. 切片的本质

切片底层指向的是一个数组,使用切片其实就是数组的引用。 切片底层维护着三个属性:

  • 指针(执行一个数组)
  • 长度len
  • 容量cap

5. 对切片进行切片化的本质

对切片化得到的不同切片,底层其实指向的是同一个数组,只是指针指向的数组位置不同。

package main  
  
import "fmt"  
  
func main() {  
    slice1 := []int{1, 2, 3, 4, 5}  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice2 := slice1[1:3]  // 切片化
    fmt.Println(slice2, len(slice2), cap(slice2))  
    slice2[0] = 100  // 更改元素是否会影响到slice1
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
}
/*
[1 2 3 4 5] 5 5
[2 3] 2 4
[1 100 3 4 5] 5 5
[100 3] 2 4
*/

6. 对切片进行动态扩容为什么会改变底层原数组

动态扩容后会发生什么?

  • 容量翻倍增长
  • 更改底层数组(底层数组替换为新的数组)
package main  
  
import "fmt"  
  
func main() {  
    var slice1 []int // 定义一个切片,但不初始化  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 1)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 2)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 3)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 4)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 5)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
}
/*
[] 0 0
[1] 1 1
[1 2] 2 2
[1 2 3] 3 4
[1 2 3 4] 4 4
[1 2 3 4 5] 5 8
*/

由打印出来的cap值可以看出容量是翻倍扩容的。

package main  
  
import "fmt"  
  
func main() {  
    var slice1 []int  
    slice1 = append(slice1, 1)  
    slice1 = append(slice1, 2)  
    slice1 = append(slice1, 3)  
    slice1 = append(slice1, 4)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice2 := slice1[1:3]  
    slice2[0] = 100  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
    slice1 = append(slice1, 5)  // 此时slice1容量扩充,是否会改变底层数组呢?
    slice2[0] = 99  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
  
}
/*
[1 2 3 4] 4 4
[1 100 3 4] 4 4
[100 3] 2 3
[1 100 3 4 5] 5 8
[99 3] 2 3
*/
  • 可以看到扩容后,改变slice2元素,slice1不受影响,说明底层这两个切片已经不指向同一个数组了。
  • 说明每次切片扩容后,底层都会重新创建一个数组。

7. 切片在实际应用中,怎么用更好,为什么?

  • 函数参数用切片,而不要用数组。
    • 切片其实就是引用传值,不涉及拷贝,性能更好。
  • 如果不知道切片长度,初始化时尽量给定一个容量:make([]int, 0, 20)
    • 避免频繁扩容影响性能。
  • 如果知道切片长度,初始化可以只指定长度:make([]int, 20)
    • 指定len后,cap和len相同。

8. 如何初始化一个n×n的多维数组?

arr := make([][]int, n)
for i := 0;i < n;i ++ {
	arr[i] = make([]int, n)
}