非零基础自学Golang

第6章 Go语言内置容器

6.2 切片

相对于数组,切片(slice)是一种更方便和强大的数据结构,它同样表示多个同类型元素的连续集合,但是切片本身并不存储任何元素,而只是对现有数组的引用

切片结构包括:地址、长度和容量

  • 地址:切片的地址一般指切片中第一个元素所指向的内存地址,用十六进制表示。
  • 长度:切片中实际存在元素的个数。
  • 容量:从切片的起始元素开始到其底层数组中的最后一个元素的个数。

切片的长度和容量都是不固定的,可以通过追加元素使切片的长度和容量增大。

切片主要有三种生成方式:

  1. 从数组生成一个新的切片;
  2. 从切片生成一个新的切片;
  3. 直接生成一个新的切片。
6.2.1 从数组生成一个新的切片

从数组或切片生成新的切片语法格式如下:

slice [开始位置:结束位置]

对于动手写6.1.2中长度为3的student数组,我们可以生成一个新的切片student1,使用len()函数可获得当前切片长度,cap()函数可获得当前切片容量。

[ 动手写 6.2.1]

package main

import "fmt"

func main() {

   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[1:2]

   fmt.Println("student 数组:", student)
   fmt.Println("student1 切片:", student1)

   fmt.Println("student数组地址为:", &student[1])  //取student[1] 元素的地址
   fmt.Println("student1切片地址为", &student1[0]) // 取student1[0] 元素的地址

   fmt.Println("student1 切片长度为: ", len(student1))
   fmt.Println("student1 切片容量为: ", cap(student1))

}

运行结果

在这里插入图片描述

根据运行结果,我们可以归纳出从数组或切片生成新的切片有如下特性:

  • 新生成的切片长度:结束位置-开始位置。
  • 新生成的切片取出的元素不包括结束位置对应的元素
  • 新生成的切片是对现有数组或切片的引用,其地址与截取的数组或切片开始位置对应的元素地址相同。
  • 新生成的切片容量指从切片的起始元素开始到其底层数组中的最后一个元素的个数。
6.2.2 从切片生成一个新的切片

我们重新从student数组生成student1切片,再从student1切片生成student2切片。

[ 动手写 6.2.2]

package main

import "fmt"

func main() {

   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[1:3]
   var student2 = student1[0:1]

   fmt.Println("student数组:", student[:])
   fmt.Println("student1切片:", student1[:])
   fmt.Println("student2切片:", student2[:])
   fmt.Println("student数组地址为", &student[1])
   fmt.Println("student1切片地址为", &student1[0])
   fmt.Println("student2切片地址为", &student2[0])
   fmt.Println("student1切片长度为:", len(student1))
   fmt.Println("student1切片容量为:", cap(student1))
   fmt.Println("student2切片长度为:", len(student2))
   fmt.Println("student2切片容量为:", cap(student2))
}

运行结果

在这里插入图片描述

为了将student的最后一个元素也取到,student1切片的结束位置设为了3。

根据结果,我们可以发现student2切片仍然是对底层数组student的引用。

另外,也可以通过slice[:]来表示切片本身。

6.2.3 直接生成一个新的切片

【1】声明切片

切片的声明格式如下:

var 切片变量名 []元素类型

[ 动手写 6.2.3 ]

package main

import "fmt"

func main() {

   var student []int
   fmt.Println("student切片: ", student)

   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))

   fmt.Println("判定student切片是否为空:", student == nil)
}

运行结果

在这里插入图片描述

从运行结果可以知道,切片声明后其内容为空,长度和容量均为0。

【2】初始化切片

① 在声明的同时初始化

我们可以在声明切片的同时进行初始化赋值,示例如下:

[ 动手写 6.2.4]

package main

import "fmt"

func main() {

   var student = []string{"Tom", "Ben", "Peter"}

   fmt.Println("student切片: ", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
   fmt.Println("判定student切片是否为空:", student == nil)
}

运行结果

在这里插入图片描述

② 使用 make() 函数初始化

声明完切片后,可以通过内建函数make()来初始化切片,格式如下:

make([]元素类型, 切片长度, 切片容量)

注意:切片的容量值必须大于等于切片长度值,否则程序会报错。对于切片的容量应该有个大概的估值,若容量值过小,对切片的多次扩充会造成性能损耗。

[ 动手写 6.2.5]

package main

import "fmt"

func main() {
   var student []int

   student = make([]int, 2, 10)
   fmt.Println("student切片: ", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
   fmt.Println("判定student切片是否为空:", student == nil)
}

运行结果

在这里插入图片描述

参照动手写6.2.3,我们可以发现student切片在初始化后,自动填充了0值且不再为空。

6.2.4 为切片添加元素

Go语言中,我们可以使用append()函数来对切片进行元素的添加。

当切片不能再容纳其他元素时(即当前切片长度值等于容量值),下一次使用append()函数对切片进行元素添加,容量会按2倍数进行扩充。

[ 动手写 6.2.6]

package main

import "fmt"

func main() {

   student := make([]int, 1, 1)

   for i := 0; i < 8; i++ {

      student = append(student, i)
      fmt.Println("当前切片长度: ", len(student), "当前切片容量: ", cap(student))
   }
}

运行结果

在这里插入图片描述

现在我们参照动手写6.2.1,对程序稍加改动。

[ 动手写 6.2.7 ]

package main

import "fmt"

func main() {

   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[0:1] //从student 数组生成切片 student1

   fmt.Println("student 数组: ", student)
   fmt.Println("student1 切片: ", student1)

   student1 = append(student1, "Danny") //对student1 切片的元素添加, 会覆盖引用数组对应的元素

   fmt.Println("扩充Danny 后的student1切片: ", student1, ", 切片长度为: ", len(student1), "切片容量为: ", cap(student1))

   fmt.Println("扩充Danny 后的student数组: ", student)
}

运行结果

在这里插入图片描述

由于student1切片是从student数组生成(即对student数组的引用),为student1添加元素会覆盖student数组中对应的元素。

所以,如果切片是从其他数组或切片生成,新切片的元素添加需要考虑对原有数组或切片中数据的影响。

6.2.5 从切片删除元素

由于Go语言没有为删除切片元素提供方法,所以需要我们手动将删除点前后的元素连接起来,从而实现对切片中元素的删除。

[ 动手写 6.2.8]

package main

import "fmt"

func main() {

   var student = []string{"Tom", "Ben", "Peter", "Danny"}

   student = append(student[0:1], student[2:]...)
   fmt.Println("student 切片: ", student)
   fmt.Println("student 切片长度: ", len(student))
   fmt.Println("student 切片容量: ", cap(student))
}

运行结果

在这里插入图片描述

其中append()函数中传入的省略号代表按student切片展开,该行代码等价于:

student = append(student[0:1],student[2],student[3])

如果需要清空切片中的所有元素,可以把切片的开始下标和结束下标都设为0来实现:

[ 动手写 6.2.9]

package main

import "fmt"

func main() {
   var student = []string{"Tom", "Ben", "Peter", "Danny"}

   student = student[0:0]
   fmt.Println("student 切片: ", student)
   fmt.Println("student 切片长度: ", len(student))
   fmt.Println("student 切片容量: ", cap(student))
}

运行结果

在这里插入图片描述

6.2.6 遍历切片

切片的遍历和数组类似,可以通过切片下标来进行遍历。切片下标同样从0开始,第一个元素的数组下标为0,第二个元素的数组下标为1,以此类推。

[ 动手写 6.2.10 ]

package main

import "fmt"

func main() {

   var student = []string{"Tom", "Ben", "Peter", "Danny"}

   for k, v := range student {
      fmt.Println("切片下标: ", k, ", 对应元素 : ", v)
   }
}

运行结果

在这里插入图片描述