Golang基础-数据结构-切片(slice)

    科技2022-08-08  105

    定义

    为什么需要切片? 保存不确定长度的数据。切片就是动态数组,它的长度并不固定,我们可以随意向切片中追加元素,而切片会在容量不足时自动扩容。

    切片是数组的一个引用,是引用类型

    切片的定义:var 变量名 []元素类型

    一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。

    例如:

    var a []string //声明一个字符串切片 var b = []int{} //声明一个整型切片并初始化 var c = []bool{false, true} //声明一个布尔切片并初始化 var d = []bool{false, true} //声明一个布尔切片并初始化 fmt.Println(a) //[] fmt.Println(b) //[] fmt.Println(c) //[false true] fmt.Println(a == nil) //true fmt.Println(b == nil) //false fmt.Println(c == nil) //false // fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较 从底层来说 相当于一个结构体

    切片的长度及容量

    通过len()函数求长度,cap()函数求切片的容量

    例如:

    arr := [5]int{1, 2, 3, 4, 5} s := arr[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) //s:[2 3] len(s):2 cap(s):4

    注意判断切片是否为空,可以通过len(slice)==0来判断。

    切片的表达式

    变量名[low,high] 其中左包含 右不包含

    例如:

    arr :=[...]int{0,1,2,3,4,5,6,7,8,9} fmt.Println("arr[2:6]=",arr[2:6]) // arr的起始下标2-6 但不包含6即2-5 // arr[2:6]= [2 3 4 5] fmt.Println("arr[:6]=",arr[:6]) // arr的从0到5 等价于arr[0:6] // arr[:6]= [0 1 2 3 4 5] fmt.Println("arr[2:]=",arr[2:]) // arr的2到最末 等价于arr[2:len(arr)] // arr[2:]= [2 3 4 5 6 7 8 9] fmt.Println("arr[:]=",arr[:]) // arr全部元素 等价于arr[0:len(arr)] // arr[:]= [0 1 2 3 4 5 6 7 8 9]

    初始化

    1.通过下标的方式获得数组或者切片的一部分; 2.使用字面量初始化新的切片; 3.使用关键字 make 创建切片:

    arr[0:3] or slice[0:3] slice := []int{1, 2, 3} slice := make([]int, 10)

    使用make关键字具体方法:

    make([]T, size, cap)

    其中:T:切片的元素类型 size:切片中元素的数量 cap:切片的容量 注意:容量部分可以省略,在这种情况下,容量将等于长度。

    例如:

    make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len]

    在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。

    遍历

    方法类似于数组的遍历

    var arr [5]int arr = [...]int{10, 20, 30, 40, 50} slice := arr[1:4] // for循环 for i := 0; i < len(slice); i++ { fmt.Printf("slice[%v]=%v", i, slice[i]) } // for-range 遍历 for i, v := range slice { fmt.Printf("i=[%v] v=%v ", i, v) }

    追加(append)

    Go语言的内建函数append()可以为切片动态添加元素。 可以添加一个或者多个元素,还可以添加另一个切片的元素。

    var s []int s = append(s, 1) // [1] s = append(s, 2, 3, 4) // [1 2 3 4] s2 := []int{5, 6, 7} //添加切片 s = append(s, s2...) // [1 2 3 4 5 6 7]

    注意 通过var声明的零值切片可以在append()函数直接使用,无需初始化。

    通常我们并不知道append调用是否导致了内存的重新分配,因此我们也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同样,我们不能确认在原先的slice上的操作是否会影响到新的slice。因此,通常是将append返回的结果直接赋值给输入的slice变量。----《Go语言圣经》

    扩容策略: 1.如果期望容量大于当前容量的两倍就会使用期望容量 2.如果当前切片的长度小于 1024 就会将容量翻倍 3.如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量

    拷贝(copy)

    Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中。

    copy(destSlice, srcSlice []T)

    其中:T:切片的元素类型 srcSlice: 数据来源切片 destSlice: 目标切片

    // copy()复制切片 a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5]

    删除

    Go语言中并没有删除切片元素的专用方法,可以使用切片本身的特性来删除元素。

    a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) fmt.Println(a) //[30 31 33 34 35 36 37] //删除头部元素 a = a[1:] //删除尾部元素 a = a[:len(a)-1] //要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

    slice可以模拟stack

    stack = append(stack, v) // push v top := stack[len(stack)-1] // top of stack stack = stack[:len(stack)-1] // pop

    案例

    反转数组

    func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } } a := [...]int{0, 1, 2, 3, 4, 5} reverse(a[:]) fmt.Println(a) // "[5 4 3 2 1 0]"

    将slice元素循环向左旋转n个元素 三次调用reverse反转函数,第一次是反转开头的n个元素,然后是反转剩下的元素,最后是反转整个slice的元素。(如果是向右循环旋转,则将第三个函数调用移到第一个调用位置就可以了。)

    s := []int{0, 1, 2, 3, 4, 5} // Rotate s left by two positions. reverse(s[:2]) reverse(s[2:]) reverse(s) fmt.Println(s) // "[2 3 4 5 0 1]"

    判断两个slice是否含有全部相等元素 注意:不能使用==操作符来判断。标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较

    func equal(x, y []string) bool { if len(x) != len(y) { return false } for i := range x { if x[i] != y[i] { return false } } return true }

    一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值。

    var s []int // len(s) == 0, s == nil s = nil // len(s) == 0, s == nil s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil

    删除元素后不用保持原来顺序,可以简单的用最后一个元素覆盖被删除的元素。

    func remove(slice []int, i int) []int { slice[i] = slice[len(slice)-1] return slice[:len(slice)-1] } func main() { s := []int{5, 6, 7, 8, 9} fmt.Println(remove(s, 2)) // "[5 6 9 8] }

    将在原有slice内存空间之上返回不包含空字符串的列表

    func nonempty(strings []string) []string { i := 0 for _, s := range strings { if s != "" { strings[i] = s i++ } } return strings[:i] }

    入的slice和输出的slice共享一个底层数组。这可以避免分配另一个数组,不过原来的数据将可能会被覆盖。

    data := []string{"one", "", "three"} fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]` fmt.Printf("%q\n", data) // `["one" "three" "three"]`

    因此我们通常会这样使用nonempty函数:data = nonempty(data)。

    nonempty函数也可以使用append函数实现:

    func nonempty2(strings []string) []string { out := strings[:0] // zero-length slice of original for _, s := range strings { if s != "" { out = append(out, s) } } return out }

    无论如何实现,以这种方式重用一个slice一般都要求最多为每个输入值产生一个输出值,事实上很多这类算法都是用来过滤或合并序列中相邻的元素。 以上案例代码片段来自《Go语言圣经》

    Processed: 0.008, SQL: 8