Golang 切片

切片( slice ),是对数组的抽象。                   // 数组长度固定、不可改变,在一些特定场景中,这种集合并不实用

// 数组求和
func arraySum(arr [3]int) int {
	// 这个求和函数只能接收 [3]int 类型,其他的类型都不支持
	sum := 0
	for _, v := range arr {
		sum += v
	}
	return sum
}

func main() {
	// 数组 arr 中只能有三个元素,不能再添加新元素了
	arr := [3]int{1, 2, 3}
	result := arraySum(arr)
	fmt.Println(result) // 6
}

Then,Golong 提供了一种灵活、功能强悍的内置类型:切片( 可以简单理解为:一个未指定大小的( 动态 )数组

  • len() 函数:切片是可索引的,可以由 len() 方法获取长度
  • cap() 函数:切片,提供了计算容量的方法 cap(),可以测量切片最长可以达到多少

与数组相比,切片的长度是不固定的,可以追加元素( 追加元素时,可能使切片的容量增大,支持自动扩容 )

另,切片,是一个引用类型,进行函数传参时,传递的是引用


切片的声明与初始化

(1)声明

	// 方式1:常规声明 var identifier []type
	var slice []int    // 切片不需要说明长度
	fmt.Println(slice) // [],切片在未初始化之前默认为 nil,长度为 0

	// 方式2:使用make函数来创建切片:make([]T, length, capacity)
	var slice1 []int = make([]int, 3)     // length,是数组的长度,也是切片的初始长度
	slice2 := make([]int, 3, 10)          // 也可以指明容量( 可选 )
	fmt.Println(slice1)                   // [0 0 0]
	fmt.Println(len(slice1), cap(slice1)) // 切片的长度为3,容量为3
	fmt.Println(slice2)                   // [0 0 0]
	fmt.Println(len(slice2), cap(slice2)) // 切片的长度为3,容量为10

(2)切片的初始化

	//(1)直接初始化
	slice3 := []int{1, 2, 3}
	fmt.Println(slice3) // [1 2 3] 

	//(2)arr[:]  通过引用数组,初始化切片
	arr4 := [...]int{1, 2, 3, 4, 5}
	slice4 := arr4[:]
	fmt.Println(slice4) // [1 2 3 4 5]

	// arr[startIndex:endIndex]  从数组的下标[startIndex,endIndex)进行引用,创建一个新的切片
	// arr[startIndex:]  默认 endIndex,表示一直到 arr 的最后一个元素
	// arr[:endIndex]  默认 startIndex,表示从 arr 的第一个元素开始
	slice5 := arr4[1:4]
	fmt.Println(slice5) // [2 3 4]

	// (3)通过引用切片,初始化切片
	slice6 := slice5[1:]
	fmt.Println(slice6) // [3 4]

切片的使用

「 切片的长度和容量 」切片拥有自己的长度和容量                                     // 切片的扩容策略

  • 可以使用内置函数 len() ,求切片的长度
  • 可以使用内置函数 cap() ,求切片的容量
	slice1 := []int{1, 2, 3, 4, 5}
	fmt.Println(len(slice1)) // 5
	fmt.Println(cap(slice1)) // 5

	slice2 := make([]int, 3, 10)
	fmt.Println(len(slice2))  // 3
	fmt.Println(cap(slice2))  // 10

「 切片截取 」                                                   // 切片的本质 & 切片表达式( a[low:high] )

	slice7 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 初始化一个切片
	fmt.Println(slice7)                           // 原始切片 [0 1 2 3 4 5 6 7 8 9]
	fmt.Println(slice7[1:5])                      // 截取切片索引[1-5) [1 2 3 4]
	fmt.Println(slice7[:5])                       // 截取索引从开始到5  [0 1 2 3 4]
	fmt.Println(slice7[5:])                       // 截取索引从5到最后  [5 6 7 8 9]
	fmt.Println(slice7)                           // 截取后,生成了新的切片,对原切片没什么影响 [0 1 2 3 4 5 6 7 8 9]

「 为切片添加元素 」可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素( 后面加… )

	slice8 := append(slice7, 10)
	fmt.Println(slice8)                   // 追加一个切片元素 [0 1 2 3 4 5 6 7 8 9 10]
	fmt.Println(len(slice7), cap(slice7)) // 切片长度 10,切片容量 10
	fmt.Println(len(slice8), cap(slice8)) // 追加了一个元素,切片长度 11,切片容量是之前的两倍
	slice8 = append(slice8, 11, 12, 13, 14, 15)
	fmt.Println(slice8) // 追加多个切片元素

	a := []int{1, 2, 3, 4, 5}
	b := []int{6, 7, 8, 9}
	c := append(a, b...)  // 可以添加另一个切片中的元素( 后面加… )
	fmt.Println(c)  // [1 2 3 4 5 6 7 8 9]

「 从切片中删除元素 」Golang 中,并没有删除切片元素的专用方法,可以利用切片本身的特性来操作

	// 从切片 x 中删除索引为 index 的元素,操作方法是 x = append(x[:index], x[index+1:]...)
	
	x := []int{1, 2, 3, 4, 5, 6}
	fmt.Println(x) // [1 2 3 4 5 6]
	// 需求:删除索引为 2 的元素,即,值 3
	x = append(x[:2], x[3:]...)
	fmt.Println(x) // [1 2 4 5 6]

「 拷贝切片 」copy 函数,是将一个切片的数据,复制到另外一个切片空间中

	slice9 := []int{1, 2, 3, 4, 5}
	fmt.Println(slice9) // [1 2 3 4 5]

	// 方式1:直接赋值,赋值的是引用,修改拷贝后的切片,会影响原切片
	var slice10 []int // 声明slice10
	slice10 = slice9
	fmt.Println(slice10)
	slice10[0] = 100
	fmt.Println(slice10) // [100 2 3 4 5]
	fmt.Println(slice9)  // [100 2 3 4 5]

	// 方式2:引用切片,修改拷贝后的切片,也会影响原切片
	slice9 = []int{1, 2, 3, 4, 5} // 重新初始化  [1 2 3 4 5]
	slice11 := slice9[:]
	fmt.Println(slice11) // [1 2 3 4 5]
	slice11[0] = 20
	fmt.Println(slice11) // [20 2 3 4 5]
	fmt.Println(slice9)  // [20 2 3 4 5]

	// 方式3:copy 函数:将一个切片的数据,复制到另外一个切片空间中
	slice9 = []int{1, 2, 3, 4, 5} // 重新初始化  [1 2 3 4 5]

	var slice12 []int
	copy(slice12, slice9)
	fmt.Println(slice12) // [],因为slice12为空,没有空间接收slice9

	slice13 := make([]int, 3, 10) // 初始长度为3,最大容量为10
	copy(slice13, slice9)
	fmt.Println(slice13) // [1 2 3]  空间不足,则只接收能接收的

	slice14 := make([]int, 7)
	copy(slice14, slice9)
	fmt.Println(slice14) // [1 2 3 4 5 0 0]  空间足够

「 反转切片 」切片,是一个引用类型,进行函数参数传递时,传递的是引用

func main() {
	slice15 := []int{1, 2, 3, 4, 5}

	// 通过指针传递(推荐),会影响原有切片
	slice16 := rev(&slice15)
	fmt.Println(slice15) // [5 4 3 2 1]
	fmt.Println(slice16) // [5 4 3 2 1]

	// 当然,也可以传递一个值 rev(slice15),这样,也会对原切片产生影响,对rev函数做相应类型调整即可
	// 切片,是一个引用类型,所以,进行函数参数传递时,传递的是引用 ?!
	slice15 = []int{1, 2, 3, 4, 5}
	fmt.Println(slice15) // [1 2 3 4 5]
	slice17 := revFunc(slice15)
	fmt.Println(slice15) // [5 4 3 2 1]
	fmt.Println(slice17) // [5 4 3 2 1]
}

// 反转切片(指针形式)
func rev(slice *[]int) []int {
	fmt.Println(*slice) // [1 2 3 4 5]
	for i, j := 0, len(*slice)-1; i < j; i, j = i+1, j-1 {
		(*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
	}
	return *slice
}

// 反转切片(值形式)
func revFunc(slice []int) []int {
	fmt.Println(slice)
	for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
		slice[i], slice[j] = slice[j], slice[i]
	}
	return slice
}