3.5 Slice 切片
Slice切片
Go中的数组和PHP中的数组很不一样,没法排序,没法添加。不过Go中有一种数据类型叫做Slice切片,这个数据类型很类似于PHP中的数组。我个人感觉也是利用率比较高的数据类型。
Slice切片实际上是对一个数组上的连续一段的引用,一个未初始化的分片的值是nil。一个切片,一旦初始化后它总是关联着一个容纳其元素的底层数组,所以一个分片和它的数组共享存储。
定义
slice := make([]type, start_length, capacity)//其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。
//基本方法
var s1 []string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
//数组引用
array:=[...]string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
s2 := array[:]
//make或者new定义。
//make的调用会创建一个隐含的数组,Slice就是引用的这个数组。
make([]string, 5, 10) //这两种方法定义的Slice是相等的
new([10]string)[0:5]
append
在切片后面追加数据,方法有两种
slice = append(slice, elem1, elem2)//切片后面追加相同的type的元素
slice = append(slice, anotherSlice…)//切片后面追加切片
第一种用法中,第一个参数为slice,后面可以添加多个参数。
如果是将两个slice拼接在一起,则需要使用第二种用法,在第二个slice的名称后面加三个点,而且这时候append只支持两个参数,不支持任意个数的参数。
长度计算
初次使用Go的时候,容易对len和cap混淆,感觉一维切片的长度和容量是一回事,其实不然。
切片是一种结构体(我们会在后面具体讲什么是结构体),大体结构像这样:
struct slice{
ptr *Elem //指针
len int //长度
cap int //容量
}
先看一个小例子:pro03_5_3.go
package main
import "fmt"
func main() {
slice := []int{5}
slice = append(slice, 7)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
slice = append(slice, 9)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
s1 := append(slice, 11)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
s2 := append(slice, 12)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
}
通过结果图,我们可以看到len和cap的长度是不一样的。但是他们所指向的地址是相同的。
通过上面的例子还引出一个有趣的问题。把程序稍微修改一下:pro03_5_4.go
package main
import "fmt"
func main() {
slice := []int{5}
slice = append(slice, 7)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
slice = append(slice, 9)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
s1 := append(slice, 11)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
s2 := append(slice, 12)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
}
输出结果:slice: [5 7 9] s1: [5 7 9 12] s2 [5 7 9 12]
难道s1不应该是[5 7 9 11]吗?这个时候在看看上面的显示结果图的地址
cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s1) = 0xc082002760
cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s2) = 0xc082002760
地址一样,OMG,这个是怎么回事?我来解释一下,append的实现原理:本质上append是在原有空间中添加,若空间不足时,采用 newSlice := make([]int, len(slice), 2*len(slice)+1)的方式进行扩容。 cap(slice) = 2 len(slice) = 2 ptr(slice) = 0xc082006290 //原始地址 cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 //容量不够,扩容 cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s1) = 0xc082002760 //容量够了,直接追加 cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s2) = 0xc082002760 //容量够了,直接追加
由于s1和s2的地址指向是一样的,所以s1的内容和我们原来想象的不一样。这个就是使用指针需要注意的。 程序换一下:pro03_5_5.go
package main
import "fmt"
func main() {
slice := []int{5}
slice = append(slice, 7)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
slice = append(slice, 9)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
s1 := make([]int, len(slice))
copy(s1, slice)
s1 = append(s1, 11)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
s2 := make([]int, len(slice))
copy(s2, slice)
s2 = append(s2, 12)
fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
}
运算的结果:slice: [5 7 9] s1: [5 7 9 11] s2: [5 7 9 12]
这次正确了。从这个例子我们可以清晰的看出,切片就是数组的指针。
排序
 ; Go的切片排序是根据类型来排的, GO分别提供了 sort.Ints() 、 sort.Float64s() 和 sort.Strings() 函数, 默认都是从小到大排序。(没有 sort.Float32s() 函数。) pro03_5_6.go
package main
import (
"fmt"
"sort"
)
func main() {
array := [...]string{"vb", "vc++", "Python", "java", "C lang", "D lang", "Go lang"}
slice := array[:]
fmt.Println(slice, "ptr(slice) =", &slice[0])
sort.Strings(slice)
fmt.Println(slice, "ptr(slice) =", &slice[0])
}
我们看到切片的地址没有变化,但是值发生了变化。 实际上在发生sort的时候,执行了三个方法 : Len() 求长度、 Less(i,j) 比较第 i 和 第 j 个元素大小的函数、 Swap(i,j) 交换第 i 和第 j 个元素的函数。
在Go默认的排序方式是从小到大,从大到小的方式稍微复杂一点。pro03_5_7.go
array := [...]string{"vb", "vc++", "Python", "java", "C lang", "D lang", "Go lang"}
slice := array[:]
sort.Strings(slice) //正序排序
sort.Sort(sort.Reverse(sort.StringSlice(slice))) //倒序排序
其他类型的如下:
sort.Sort(sort.Reverse(sort.IntSlice(slice)))
sort.Sort(sort.Reverse(sort.Float64Slice(slice)))
slice,类似python的list.index()方法
golang里面默认没有这个方法,这个方法就是为了查找slice里面是否包含所查询的参数,如果找到返回这个参数所在的index,未找到返回-1。 具体函数如下:
type Element interface{}
// 类似python的list.index()方法,找出slice里面是否包含查询的参数
// 返回值是-1表示没有搜索到。
func SliceIndex(a Element, i interface{}) (result int) {
result = -1
if b, ok := a.([]int); ok {
if c, ok1 := i.(int); ok1 {
for indexC, v := range b {
if v == c {
result = indexC
break
}
}
}
}
if b, ok := a.([]string); ok {
if c, ok1 := i.(string); ok1 {
for indexC, v := range b {
if v == c {
result = indexC
break
}
}
}
}
if b, ok := a.([]float64); ok {
if c, ok1 := i.(float64); ok1 {
for indexC, v := range b {
if v == c {
result = indexC
break
}
}
}
}
return
}
}
会有后续的更改,更改的功能去这里看
一件趣事引发的程序
无意间看到了这样一则新闻:
程序猿招租广告 手机号竟是一串代码
日前,有程序员在南京某小区张贴了一份合租广告,表示屋内均是IT行业人士,喜欢安静,所以要求来租者最好是同行或者刚毕业的年轻人。
而在联系方式部分,这条广告并没有提供明显的手机号,而是一段代码。。。
这个一看就是第一的C语言练习题目,基本就是Go的slice俩习题呀,我们来做一下:
func main() {
var arr = []int{8, 2, 1, 0, 3} //手机号码只是由这5个数字组成
var index = []int{2, 0, 3, 2, 4, 0, 1, 3, 2, 3, 3} //手机号码各位的顺序
tel := ""
for _, i := range index {
tel += strconv.Itoa(arr[i])
}
fmt.Println("联系方式:", tel)
}
运行结果如下:
联系方式: 18013820100