Go语言入门-常用数据结构之切片slice

Go语言入门-常用数据结构之切片slice

定义

概述

A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array.The number of elements is called the length of the slice and is never negative. The value of an uninitialized slice is nil. --切片是对底层数组连续的个段的描述符,并提供对该数组中元素的顺序(下标)访问。 元素的数量称为切片的长度,绝不为负。 未初始化的片的值为nil。
这句话体现数组的几个特点

  1. 切片是对底层数组的一个引用,因此切片是引用类型
  2. 切片中元素是被编号的(具备下标)
  3. 切片有长度

切片(Slice)是通过引用底层数组,设定相关的属性,把数据读写操作些安定在指定区域中,切片本是是个只读对象。

切片定义语法

var Slice []Type // 声明切片类型,nil,不会分配空间
array[low:high:max] //切片表达式获取切片
//以下语句等价
make([]Type, len, cap) // 使用make定义切片,分配内存,并初始化
new([cap]Type)[0 : len] // 使用new定义切片,分配内存,并初始化。


切片定义示例
  • 示例1
    普通切片的声明,类型是[]int 值是nil代表没有引用到具体的底层数组。
func main() {
    var slc1 []int //声明一个int切片
    //slic1 类型是[]int 但是值是nil, 并为初始化。
    fmt.Printf("%#v", slc1)
}
/**
output:
[]int(nil) //没有引用到底层数组
*/
  • 示例2
    普通切片后声明,并且通过初始化表达式进行初始化。
func main() {
    var slc1 []int //声明一个int切片 没有初始化
    //slic1 类型是[]int 但是值是nil
    fmt.Printf("%#v\n", slc1)
    var slc2 = []int{} // 定义一个整型切片并初始化。切片引用runtime.zerobase。
    fmt.Printf("%#v\n", slc2)
    //切片之间不支持判等
    //fmt.Println(slc1 == slc2) //Invalid operation: slc1 == slc2 (operator == not defined on []int)
    fmt.Println(slc1 == nil) //没有初始化所以 nil == nil
    fmt.Println(slc2 == nil) //初始化以后 slc2引用runtime.zerobase(某一地址),而不是0x00所以为false 
}
/**
[]int(nil)
[]int{}
true
false
 */
  • 示例3
    通过make定义切片,表达式make([]Type, len, cap) cap省略以后默认容量等于len。
func main() {
    //make 定义slc1变量。长度2,
    var slc1 = make([]int, 2) //没有指定cap,cap等于len
    fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
    var slc2 = make([]int, 2, 5)
    fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
}
/**
output:
slc1 len=[2] cap=[2] value=[[]int{0, 0}]
slc2 len=[2] cap=[5] value=[[]int{0, 0}]
 */
  • 示例4
    通过new来定义切片( 本质也是切片表达式)
func main() {
    //new定义slc1变量。长度2,
    var slc1 []int = new([2]int)[:] //实际上是定义一个长度为2的int数组,然后通过切片表达式定义切片
    fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
    var slc2 = new([2]int)[0:2]
    fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
}
/**
slc1 len=[2] cap=[2] value=[[]int{0, 0}]
slc2 len=[2] cap=[2] value=[[]int{0, 0}]
 */
  • 示例5
    通过切片表达式构造切片
func main() {
    // 切片表达式 array[low:high:max]
    //完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。
    //low省略从0开始,high省略则为len(array),cap省略则为 cap(array) - low
    //切片为右半开区间
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    //省略了low high cap 则引用整个底层数组
    slc1 := array[:]
    fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
    //从array数组下标2,到下标4-1(不包括4),默认最大容量为 10-2(cap(array) - low)
    slc2 := array[2:4]
    fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
    //从array数组下标2,到下标4-1(不包括4),最大容量为 5-2(high - low)
    slc3 := array[2:4:5]
    fmt.Printf("slc3 len=[%d] cap=[%d] value=[%#v]\n", len(slc3), cap(slc3), slc3)
    //从array数组下标2,到下标4-1(不包括4),最大容量为 10-2(high - low)
    slc4 := array[2:4:10]
    fmt.Printf("slc4 len=[%d] cap=[%d] value=[%#v]\n", len(slc4), cap(slc4), slc4)
    //省略high和max,从array数组下标2,到array[len(array) -1 ],最大容量为 10-2(high - low)
    slc5 := array[2:]
    fmt.Printf("slc5 len=[%d] cap=[%d] value=[%#v]\n", len(slc5), cap(slc5), slc5)
    //省略low和max,从array数组下标0,到array[4 -1 ],最大容量为 10-2(high - low)
    slc6 := array[:4]
    fmt.Printf("slc6 len=[%d] cap=[%d] value=[%#v]\n", len(slc6), cap(slc6), slc6)
    //省略low,从array数组下标0,到array[4 -1],最大容量为 4-0(high - low)
    slc7 := array[:4:4]
    fmt.Printf("slc7 len=[%d] cap=[%d] value=[%#v]\n", len(slc7), cap(slc7), slc7)
}
/**
slc1 len=[10] cap=[10] value=[[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}]
slc2 len=[2] cap=[8] value=[[]int{3, 4}]
slc3 len=[2] cap=[3] value=[[]int{3, 4}]
slc4 len=[2] cap=[8] value=[[]int{3, 4}]
slc5 len=[8] cap=[8] value=[[]int{3, 4, 5, 6, 7, 8, 9, 10}]
slc6 len=[4] cap=[10] value=[[]int{1, 2, 3, 4}]
slc7 len=[4] cap=[4] value=[[]int{1, 2, 3, 4}]
 */
  • 示例6
    通过切片表达式构造切片-根据字符串
func main() {
    //切片表达式 string[low:high]
    s := "erwrwer"
    //不能使用cap
    slc1 := s[:]
    fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc1), slc1)
    slc2 := s[4:]
    fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc2), slc2)
    slc3 := s[:2]
    fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc3), slc3)
    slc4 := s[1:4]
    fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc4), slc4)
    //slc1 := s[1:3:4] //Invalid operation s[1:3:4] (3-index slice of string)
    //fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc1), slc1)
}
/**
output:
slc1 len=[7] value=["erwrwer"]
slc1 len=[3] value=["wer"]
slc1 len=[2] value=["er"]
slc1 len=[3] value=["rwr"]

 */

切片表达式用在string上 cap会失效
切片表达式为 string[low:high]—实际上来说,应用在string上算是一种通过下标范围求取子串。并不是切片。因为通过表达式得到不是切片而是字符串。

因此,切片表达式用在string不会产生切片,只是产生字符串的子串。

  • 示例7
    通过切片表达式构造切片-根据切片
func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    slice := array[1:] //把slice当做数组即可
    //对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样。
    slc1 := slice[:4]
    fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
}
/**
output:
slc1 len=[4] cap=[9] value=[[]int{2, 3, 4, 5}]
 */

对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样

切片的使用

赋值与传递

  • 示例1

func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(array)
    slc1 := array[1:]
    slc2 := array[3:]
    //底层数组使用下标修改元素值,会修改底层数组的元素值
    array[4] = 9999
    //切片使用下标修改元素值,会修改底层数组的元素值
    slc1[6] = 88888
    slc2[6] = 77
    fmt.Println(slc1)
    fmt.Println(slc2)
    fmt.Println(array)
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
[2 3 4 9999 6 7 88888 9 77]
[4 9999 6 7 88888 9 77]
[1 2 3 4 9999 6 7 88888 9 77]
 */

数组关联的切片和数组本身都是共同使用一个底层数组的,因此切片类型是值传递

切片遍历

切片的遍历和数组一致,支持fori 和 for range

  • 示例1
func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(array)
    slc1 := array[1:]
    
    //for i 遍历1
    for i := 0; i < len(slc1); i++ {
        fmt.Printf("%#v ", slc1[i])
    }
    fmt.Println()
    
    //for range 遍历
    for _, v := range slc1 {
        fmt.Printf("%#v ", v)
    }
    fmt.Println()
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10
*/

切片增加元素

go语言中使用内置函数append()追加元素。

追加单个元素
  • 示例1
    追加单个元素到切片中
func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(array)
    slc1 := array[1:]
    fmt.Println(slc1)
    //切片slc1追加一个元素
    slc1 = append(slc1, 1000)
    fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
    slc1 = append(slc1, 1000)
    fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
    slc1 = append(slc1, 1000)
    fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
}
/**
[1 2 3 4 5 6 7 8 9 10]
[2 3 4 5 6 7 8 9 10]
address  0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000]
address  0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000 1000]
address  0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000 1000 1000]
 */
切片追加切片

语法:slice1 = append(slice1, slice2…)
使用…省略号来把添加的切片当成多个元素。并且两个切片的元素类型必须一致。

func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(array)
    slc1 := array[1:]
    slc2 := array[3:]
    fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
    slc1 = append(slc1, slc2...) //append的第二个参数,送切片变量,然后增加...标识批量增加切片数据
    fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
address  0xc0000044c0 [2 3 4 5 6 7 8 9 10]
address  0xc0000044c0 [2 3 4 5 6 7 8 9 10 4 5 6 7 8 9 10]
*/

切片扩容

可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
  • 如果新申请容量大于2倍的旧容量,最终容量是新申请的容量。
  • 如果旧切片的长度小于1024,则最终容量是老容量的2倍、

-----TODO

切片删除

1.切片头部的删除使用切片表达式
2.切片尾部的删除也是用切片变道时
3. 切边中间的删除比较特殊,使用append。使用的方法
slice = apend(slice[:要删除元素的起始索引], slice[:要删除的截止索引 + 1]…)

  • 示例1
func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(array)
    slc := array[:]
    fmt.Println("address ", unsafe.Pointer(&slc), slc)
    //从开头删除连续元素
    slc = slc[2:] //从开头删除数据
    fmt.Println("address ", unsafe.Pointer(&slc), slc)
    //从尾部删除连续元素
    slc = slc[:7] //从开头删除数据
    fmt.Println("address ", unsafe.Pointer(&slc), slc)
    //从中间删除元素,使用append
    
    //删掉下标是2,3的数值5和6
    //后续的789分别想前移动两位,赋值。
    slc = append(slc[0:2], slc[4:]...)
    fmt.Println("address ", unsafe.Pointer(&slc), slc)
    //底层数组元素值也会变化
    fmt.Println("address ", unsafe.Pointer(&array), array)
}
/**
[1 2 3 4 5 6 7 8 9 10]
address  0xc0000044c0 [1 2 3 4 5 6 7 8 9 10]
address  0xc0000044c0 [3 4 5 6 7 8 9 10]
address  0xc0000044c0 [3 4 5 6 7 8 9]
address  0xc0000044c0 [3 4 7 8 9]
address  0xc000016230 [1 2 3 4 7 8 9 8 9 10]

 */

切片拷贝

内建函数copy可以用来赋值前片数据,允许指向同一底层数组,允许目标取现重叠,最后复制长度以较短的切片长度为准。使用copy可以避免切片是引用类型导致修改切片后底层数据也发生变化。
不是同一底层数据的复制,所复制元素的个数以目标切片的长度为准,当源长度小于等于目标长度,全部复制,否则截断源切片多余的长度

  • 示例1-截断源长度
func main() {
    s := []int{45, 56, 7, 423, 4}
    t := [...]int{0, 0}
    d := t[:]
    fmt.Printf("sour = %#v dest=%#v\n", s, d)
    //截断源长度
    copy(d, s)
    fmt.Println("after copy")
    fmt.Printf("sour = %#v dest=%#v\n", s, d)
}
/**
output:
sour = []int{45, 56, 7, 423, 4} dest=[]int{0, 0}
after copy
sour = []int{45, 56, 7, 423, 4} dest=[]int{45, 56}
 */
  • 示例2-复制所有源数据
func main() {
    s := []int{45, 56, 7, 423, 4}
    t := [10]int{0, 0, 9:10}
    d := t[:]
    fmt.Printf("sour = %#v dest=%#v\n", s, d)
    //源切片所有都被赋值。
    copy(d, s)
    fmt.Println("after copy")
    fmt.Printf("sour = %#v dest=%#v\n", s, d)
}
/**
output:
sour = []int{45, 56, 7, 423, 4} dest=[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 10}
after copy
sour = []int{45, 56, 7, 423, 4} dest=[]int{45, 56, 7, 423, 4, 0, 0, 0, 0, 10}
 */

切片排序

go语言标准库中的sort提供了对整型、浮点型、字符串类型进行排序的函数

  • 示例1 -排序int切片
func main() {
    s := []int{45, 56, 7, 423, 4}
    fmt.Println(s)
    sort.Ints(s)
    fmt.Println("after sort")
    fmt.Println(s)
}
/**output
[45 56 7 423 4]
after sort
[4 7 45 56 423]
 */

具体sort的使用后续整理。

总结

关键点:

  • 切片表达式用在string不会产生切片,只是产生字符串的子串。
  • 对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样
  • 数组关联的切片和数组本身都是共同使用一个底层数组的,因此切片类型是值传递
  • 不是同一底层数据的复制,所复制元素的个数以目标切片的长度为准,当源长度小于等于目标长度,全部复制,否则截断源切片多余的长度

猜你喜欢

转载自blog.csdn.net/u011461385/article/details/106039405