【Golang】Golang基础语法之内建容器(二):切片

发布于:2024-12-06 ⋅ 阅读:(128) ⋅ 点赞:(0)

切片

在 Golang 当中实际上很少会用到数组,大部分情况下会直接使用切片(slices)。

Golang 当中的切片与 Python 当中的切片非常的相似(左开右闭区间),它是 array 的一个 view(视图),因此对切片进行的修改实际上会直接修改对应的 array。

一段切片的声明如下:

arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 = arr[2:6]	// 2, 3, 4, 5

一段完整的用法展示如下:

package main

import "fmt"

func updateSlice(s []int) {
	s[0] = 100
}

func main() {
	/*
		Go 当中很少使用数组
		实际上更常使用的是切片(slice)
	*/
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("arr[2:6] =", arr[2:6]) // 左开右闭区间
	fmt.Println("arr[:6] =", arr[:6])   // 左开右闭区间
	s1 := arr[2:]
	fmt.Println("s1 =", s1) // 左开右闭区间
	s2 := arr[:]            // 相当于直接将数组转为 slice
	fmt.Println("s2", s2)   // 左开右闭区间
	// slice 是对 array 的 view(视图)

	fmt.Println("After updateSlice(s1)")
	updateSlice(s1)
	fmt.Println("s1 =", s1)
	fmt.Println("arr =", arr) // 由于 s1 是对 array 的 view
	// 因此 array 的值也被修改了

	fmt.Println("After updateSlice(s2)")
	updateSlice(s2)
	fmt.Println("s2 =", s2)
	fmt.Println("arr =", arr)

	// slice 还有一个操作叫 reslice
	// 此时 s2 是一个 slice
	fmt.Println("Reslice")
	fmt.Println(s2)
	s2 = s2[:5]
	fmt.Println(s2)
	s2 = s2[2:]
	fmt.Println(s2)
}

切片的特性

以下例为例:

package main

import "fmt"

func main() {
	fmt.Println("Extending Slice: ")
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("arr =", arr)
	s1 := arr[2:6] // [2, 3, 4, 5]
	s2 := s1[3:5]  // [5, 6]
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2) // 不会报错, s2 仍然取得出来
	/* 为了理解上述行为为什么是合法的, 涉及到 Slice 的拓展知识
	slice 是对 array 的 view, 意味着 slice 知道底层的
	array, 因此 s2 仍然能取出底层 array 对应的值.
	我们称上述特性为 slice 的可拓展性.
	*/

	/* slice 的底层实现由 ptr/len/cap 构成
	- ptr 指向 slice 第一个元素的地址;
	- len 指向 slice 的长度;
	- cap 为 capacity 的缩写, 表明 slice 至多可以向后扩展的长度
	注意: slice 不可以向前拓展.
	具体的扩展方法: s[i] 不可以超越 len(s), 向后扩展不能超过 cap(s) */
	fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n", s1, len(s1), cap(s1))
	fmt.Printf("s1=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
}

注意到,可以使用 len 来获取当前切片当中存储元素的长度。可以使用 cap 得到当前切片的最大容量,如果当前切片与某个数组绑定(即,切片为数组的视图),那么从切片的起始位置开始(比如第二个位置)到数组的最大位置(比如数组为{0, 1, 2, 3, 4, 5, 6,7 },切片为{2, 3, 4, 5})是切片的 cap(2, 3, 4, 5, 6, 7),切片可以取 cap 内的值,正如上例所示。

我个人认为这不是一个很好的特性,在给定视图的基础上取视图以外的元素意义不大。

切片的操作

切片的操作可以归纳为三种,分别是创建、拷贝和删除。

我们可以创建一个空的切片,它可以不是任何数组的视图,此时的切片类似于 C++ 当中的 vector:

切片的创建

var s []int // 将 s 定义为一个 slice
for i := 0; i < 100; i++ {
	printSlice(s)			// 打印切片的相关信息
	s = append(s, 2*i+1)	// 与 vector 类似, 切片的 cap(s) 同样会在需要时翻倍增长
}

可以使用内置的 make 函数创建一个指定长度(len,实际上初始的 cap 也可以指定)的空切片:

s1 := []int{2, 4, 6, 8}
printSlice(s1)

s2 := make([]int, 16) // 我们希望构建一个长度为 16 的 slice
// 👆 但是目前我们还不知道 slice 当中的值, 此时需要使用内建的函数 make
s3 := make([]int, 10, 32) // 32 是 cap
printSlice(s2)            // len = 16, cap = 32
printSlice(s3)            // len = 10, cap = 32

切片的拷贝

可以使用内建的 copy 函数来完成切片的拷贝工作。‘

fmt.Println("Copying slice:")
copy(s2, s1)   // 系统内建的 copy 函数, 首先是 dst, 之后是 src
printSlice(s2) // 将 s1 的 2, 4, 6, 8 拷贝到了 s2 中

切片当中首尾元素的删除

可以使用赋值的方式来完成首尾元素的删除:

fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)

fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]

fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, tail)
printSlice(s2)

上述内容的完整代码如下:

package main

import "fmt"

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
	// 可以看到 slice 的底层扩容原理与 C++ 的 vector 非常相似
	// 当 cap 不足时, 将 cap 翻倍
}

func main() {
	fmt.Println("Creating Slice:")
	var s []int // 将 s 定义为一个 slice
	// 根据前面的学习内容, 我们知道 slice 是对 array 的 view
	// 但是现在没有 array 的定义, 或者说当前定义的 slice 没有和 array 绑定
	// 我推测可以直接对 slice 进行操作, 它将会自动分配一个 array
	// 正如前面 append 操作中所看到的, 当 array 达到 cap 时, 系统会分配新的 array

	// Zero value for slice is nil
	for i := 0; i < 100; i++ {
		printSlice(s)
		s = append(s, 2*i+1)
	}

	s1 := []int{2, 4, 6, 8}
	printSlice(s1)

	s2 := make([]int, 16) // 我们希望构建一个长度为 16 的 slice
	// 👆 但是目前我们还不知道 slice 当中的值, 此时需要使用内建的函数 make
	s3 := make([]int, 10, 32) // 32 是 cap
	printSlice(s2)            // len = 16, cap = 32
	printSlice(s3)            // len = 10, cap = 32

	fmt.Println("Copying slice:")
	copy(s2, s1)   // 系统内建的 copy 函数, 首先是 dst, 之后是 src
	printSlice(s2) // 将 s1 的 2, 4, 6, 8 拷贝到了 s2 中

	fmt.Println("Deleting elements from slice")
	s2 = append(s2[:3], s2[4:]...)
	printSlice(s2)

	fmt.Println("Popping from front")
	front := s2[0]
	s2 = s2[1:]

	fmt.Println("Popping from back")
	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]
	fmt.Println(front, tail)
	printSlice(s2)
}


网站公告

今日签到

点亮在社区的每一天
去签到