【学习笔记】Go切片

147 阅读7分钟

Go切片

摘要: 本文主要介绍了 Go 语言中的切片。包括切片与数组的区别,切片的定义、初始化方式(如直接初始化、从数组或切片获取、用 make 函数创建),切片长度和容量的概念及不一致的原因,切片内元素的修改、添加、删除的操作方法(如通过下标修改、用 append 函数添加和删除),切片的复制(值传递和引用传递,以及使用 copy 函数复制),还有通过修改字符串内容的方法。(由豆包进行生成)

切片和数组

  • 切片: 保存一组特定数据类型的不定长的容器
  • 数组: 保存一组特定数据类型的有定长的容器

切片的定义

切片的定义与数组的定义基本一直,只是用[]来声明。

package mai
import "fmt"
func main() {
	// 声明一个长度为3 存储int类型数据 的数组
	var arr1 [3]int
	// 声明一个 存储string类型数据 的切片
	var slice1 []string
	fmt.Printf("%v - %T - %v\n", arr1, arr1, len(arr1))
	fmt.Printf("%v - %T - %v\n", slice1, slice1, len(slice1))
	fmt.Println(slice1 == nil)  // nil 类似于 null
}
[0 0 0] - [3]int - 3 // [3]int 代表最大容量为3,存储数据类型为int的数组
[] - []string - 0  // []string 代表存储数据类型为string的切片
true

初始化切片

切片的初始化方式与数组基本一致。

3.1 切片的初始化
package main
import "fmt"
func main() {
	arr1 := [3]int{1, 2, 3}
	sliceA := []int{1, 2, 3}
	fmt.Printf("%v - %T - %v \n", arr1, arr1, len(arr1))
	fmt.Printf("%v - %T - %v \n", sliceA, sliceA, len(sliceA))
	arr2 := [...]int{0: 5, 3: 4, 5: 6}
	sliceB := []int{0: 5, 3: 4, 5: 6}
	fmt.Printf("%v - %T - %v \n", arr2, arr2, len(arr2))
	fmt.Printf("%v - %T - %v \n", sliceB, sliceB, len(sliceB))
}
[1 2 3] - [3]int - 3 
[1 2 3] - []int - 3 
[5 0 0 4 0 6] - [6]int - 6 
[5 0 0 4 0 6] - []int - 6 

切片除了通过上面这种方式进行初始化以外,还可以通过数组或者切片进行初始化

3.2 通过数组得到切片
package main
import "fmt"
func main() {
	arr1 := [5]int{1, 2, 3, 4, 5}
	sliceA := arr1[1:4]
	fmt.Printf("%v - %T - %v \n", arr1, arr1, len(arr1))
	fmt.Printf("%v - %T - %v \n", sliceA, sliceA, len(sliceA))
}
[1 2 3 4 5] - [5]int - 5 
[2 3 4] - []int - 3 
3.3 通过切片得到切片
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[2:6]
	fmt.Printf("%v - %T - %v \n", sliceA, sliceA, len(sliceA))
	fmt.Printf("%v - %T - %v \n", sliceB, sliceB, len(sliceB))
}
[1 2 3 4 5 6 7 8 9 10] - []int - 10 
[3 4 5 6] - []int - 4 
3.4 使用make函数动态创建切片
package main
import "fmt"
func main() {
	/*
		make([]元素类型, 长度, 容量)
	*/
	sliceA := make([]int, 5, 10)
	fmt.Println(sliceA)
}
[0 0 0 0 0]

切片的长度和容量

切片的长度可以通过len函数得到,而切片的容量则需要通过cap函数来得到,如果我们正常定义切片的话,其长度和容量应该是相等的。

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	fmt.Printf("%v - %v", len(sliceA), cap(sliceA))
}

但是如果我们是通过数组切片得到的切片,其长度和容量未必是相等的。

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[2:6]
	fmt.Printf("%v - %v", len(sliceB), cap(sliceB))
}
4 - 8

长度是4,容量却是8,很明显,是不一致的。

4.1 切片的长度和容量不一致的原因
  • 长度: 切片的长度就是它所包含的元素个数
  • 容量: 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数

单纯的看概念,可能难以理解,请看下面这张图

img.png 容量是指,从开始元素到末尾,假设我们截取从2-4(对应下标为[1:4]),那么应该是这样的一个图。

img_1.png 此时长度应该为3,容量却为9

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[1:4]
	fmt.Printf("%v - %v", len(sliceB), cap(sliceB))
}
3 - 9

切片内元素的修改和元素的添加

元素的修改可以通过下标的方式来进行,而元素的添加则需要通过append函数来实现。

5.1 元素的修改
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceA[5] = 150
	fmt.Println(sliceA)
}
[1 2 3 4 5 150 7 8 9 10]
5.2 使用下标来添加元素
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[1:4]
	fmt.Printf("%v - %v", len(sliceB), cap(sliceB))
}

sliceB为例,我们知道,切片的长度是3,容量为9,按常理来说,我们应该是可以通过下标来添加元素的,但是实际上并非如此。

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[1:4]
	sliceB[3] = 150
	fmt.Println(sliceB)
}
runtime error: index out of range [3] with length 3
5.3 使用append添加元素
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA[1:4]
	fmt.Println(sliceB)
	sliceB = append(sliceB, 5)
	fmt.Println(sliceB)
	// 如果要添加多个元素,用英文,进行分割
	sliceB = append(sliceB, 6, 7, 8, 9, 10, 11)
	fmt.Println(sliceB)
}
[2 3 4]
[2 3 4 5]
[2 3 4 5 6 7 8 9 10 11]
5.4 使用append添加切片
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := []int{11, 12, 13}
	// 将sliceB中的元素添加到sliceA中,注意最后的三个...,这个不能省略
	sliceA = append(sliceA, sliceB...)
	fmt.Println(sliceA)
}
[1 2 3 4 5 6 7 8 9 10 11 12 13]

切片内元素的删除

在Go语言中,目前尚未提供从切片中删除元素的函数,但是我们可以通过append来实现这个功能。

package main
import "fmt"
func main() {
	// 从sliceA中删除4和5
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceA = append(sliceA[:3], sliceA[5:]...)
	fmt.Println(sliceA)
}
[1 2 3 6 7 8 9 10]

切片的复制

在Go中,切片的复制,使用的是引用传递,所以,如果我们通过=来复制某个切片时,当副本改变,切片的内容也会发生改变。

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := sliceA
	sliceB[0] = 111
	fmt.Println(sliceA)
	fmt.Println(sliceB)
}
[111 2 3 4 5 6 7 8 9 10]
[111 2 3 4 5 6 7 8 9 10]
7.1 值传递和引用传递

在Go语言中,数组采用的是值传递,切片使用的是引用传递,接下来通过图解的方式来详细讲解值传递和引用传递
值传递

package main
import "fmt"
func main() {
	arr := [3]int{1, 2, 3}
	arr1 := arr
	arr[0] = 111
	fmt.Println(arr)
	fmt.Println(arr1)
}

以当前代码为例,通过图解方式来详细分析值传递。

  • 首先在内存中开辟一块空间用来存放{1, 2, 3}

img_2.png

  • 然后将变量arr指向内存空间中的{1, 2, 3}

img_3.png

  • arr1 := arr会在内存中在开辟一块新的空间用来存放另一组{1,2,3}

img_4.png

  • 然后再将变量arr1指向新的内存地址

img_5.png

  • 此时由于两个变量指向了两个不同的内存地址,所以我们改变任意一个变量,都不会对其他变量产生影响

引用传递

package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3}
	sliceB := sliceA
	sliceB[0] = 111
	fmt.Println(sliceA)
	fmt.Println(sliceB)
}
  • 首先还是在内存中开辟一块空间,用来存放{1, 2, 3},并将变量sliceA指向这块内存空间。

img_6.png

  • sliceB := sliceA时,Go并不会在单独创建一份内存,而是直接将sliceB也指向当前的内存空间。

img_7.png

  • 此时,由于两个变量指向的是同一个内存地址,所以,当其中一个变量的数据发生改变时,另外一个也会发生改变。
7.2 使用copy复制切片
package main
import "fmt"
func main() {
	sliceA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	sliceB := make([]int, len(sliceA), len(sliceA)+1)
	copy(sliceB, sliceA) // 将切片sliceA中的数据拷贝到sliceB中
	fmt.Println(sliceA)
	fmt.Println(sliceB)
	sliceB[0] = 111
	fmt.Println(sliceA)
	fmt.Println(sliceB)
}
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
[111 2 3 4 5 6 7 8 9 10]

通过切片的方式修改字符串中的内容

package main
import "fmt"
func main() {
	s := "你好,张先生"
	// 如果是纯英文可以使用byte,包含中文则需要使用rune
	SliceA := []rune(s) // 将字符串转为切片
	SliceA[0] = '您'
	s = string(SliceA)
	fmt.Println(s)
}