内容简介:切片是一种复合数据类型,与数组类似,存放相同数据类型的元素,但数组的大小是固定的,而切片的大小可变,可以按需自动改变大小。切片是基于底层数组实现的,是对数组的抽象。切片很小,只有三个字段的数据结构:如上图所示,一个长度为3、容量为5的整型切片的底层结构。使用内置函数创建空切片,形如:
切片是一种复合数据类型,与数组类似,存放相同数据类型的元素,但数组的大小是固定的,而切片的大小可变,可以按需自动改变大小。切片是基于底层数组实现的,是对数组的抽象。切片很小,只有三个字段的数据结构: 指向底层数组的指针 、能访问的元素个数(即 切片长度 )和允许增长到的元素个数(即 切片容量 )。
如上图所示,一个长度为3、容量为5的整型切片的底层结构。
声明与初始化
make()创建
使用内置函数创建空切片,形如:
s := make([]type, len, cap) // len 长度,cap 容量 复制代码
也可以只指定 len
,那么切片的容量和长度是一样的。Go语言提供了内置函数 len
、 cap
分别返回切片的长度和容量。
// 声明一个长度为3、容量为5的整型切片 s1 := make([]int,3,5) fmt.Println(len(s1),cap(s1)) // 输出:3 5 // 声明一个长度和容量都是5的字符串切片 s2 := make([]string,5) fmt.Println(len(s2),cap(s2)) // 输出:5 5 复制代码
切片创建完成,如果不指定字面量的话,默认值就是数组的元素的零值。 切片的容量就是切片底层数组的大小,我们只能访问切片长度范围内的元素,如第一节的图所示,长度为3的整型切片存入3个值后的结构,我们只能访问到第3个元素,剩余的2个元素需要切片扩充以后才可以访问。所以,很明显的: 容量>=长度 ,我们不能创建长度大于容量的切片。
s1 := make([]int,5,3) // 报错:len larger than cap in make([]int) 复制代码
使用字面量创建切片
使用字面量创建,就是指定了初始化的值
s := []int{1,2,3,4,5} // 长度和容量都是5的整型切片 复制代码
有没有发现,这种创建方式与创建数组类似,只不过不用指定 []
的值,这时候切片的长度和容量是相等的,并且会根据指定的字面量推导出来。 区别:
// 创建大小为10的数组 s := [10]int{1,2,3,4,5} // 创建切片 s := []int{1,2,3,4,5} 复制代码
我们也可以只初始化某一个 索引 的值:
s := []int{4:1} fmt.Println(len(s),cap(s)) // 输出:5 5 fmt.Println(s) // 输出:[0 0 0 0 1] 复制代码
指定了第5个元素为 1
,其他元素初始化为0。
基于已有的数组或者切片创建切片
使用操作符 [start,end]
,简写成 [i,j]
,表示从索引 i
,到索引 j
结束,截取已有数组或者切片的任意部分,返回一个新的切片,新切片的值包含原切片的 i
索引的值,但是不包含 j
索引的值。 i
、 j
都是可选的, i
如果省略,默认是0, j
如果省略,默认是原切片或数组的长度。 i
、 j
都不能超过这个长度值。
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("s[:]", s[:]) fmt.Println("s[2:]", s[2:]) fmt.Println("s[:4]", s[:4]) fmt.Println("s[2:4]", s[2:4]) 复制代码
输出 你可能会有个疑问:截取获得的新切片的长度和容量怎么计算呢?我们当然可以使用内置函数 len
、 cap
直接获得,如果明白了怎么计算的,我们处理问题就可以更得心应手。 对底层数组大小为 k
的切片执行 [i,j]
操作之后获得的新切片的长度和容量是: 长度: j-i
容量: k-i
就拿上一个例子的 s[2:4]
来说,原切片底层数组大小是10,所以新切片的长度是 4-2=2
,容量是 10-2=8
。 可以使用内置函数验证下:
s1 := s[2:4] fmt.Println(len(s1),cap(s1)) // 输出:2 8 复制代码
上面是使用2个索引的方法创建切片,还可以使用3个索引的方法,第3个用来限定新切片的容量,用法为 slice[i:j:k]
。
s2 := s[2:4:8] fmt.Println(s2) // 输出:[2 3] 复制代码
长度和容量如何计算:长度 j-i
,容量 k-i
。所以切片 s2
的长度和容量分别是 2
、 6
。 注意 : k
不能超过原切片(数组)的长度,否则报错 panic: runtime error: slice bounds out of range
。例如上面的例子中,第三个索引值不能超过10。 我们来看个例子:
s := []int{0, 1, 2, 3, 4, 5} fmt.Println("before,s:",s) s1 := s[1:4] fmt.Println("before,s1:",s1) s1[1] = 10 fmt.Println("after,s1:",s1) fmt.Println("after,s:",s) 复制代码
输出 :
before,s: [0 1 2 3 4 5] before,s1: [1 2 3] after,s1: [1 10 3] after,s: [0 1 10 3 4 5] 复制代码
这个例子说明,原切片和新切片是基于同一个底层数组的,所以当修改的时候,底层数组的值就会被改变,原切片的值也随之改变了。对于基于数组的切片也一样的。
我们可以看到,执行完切片动作之后,获得一个新切片,与原切片共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分。切片s
能够看到底层数组全部6个元素,而切片
s1
只能看到索引
1
及之后的全部元素,对于
s1
来说,索引
1
之前的部分是不存在。
使用切片
切片的使用方法与数组的使用方法类似,直接通过索引就可以获取、修改元素的值。
s := []int{1, 2, 3, 4, 5} fmt.Println(s[1]) // 获取值 输出:2 s[1] = 10 // 修改值 fmt.Println(s) //输出:[1 10 3 4 5] 复制代码
只能访问切片长度范围内的元素,否则报错
s := []int{1, 2, 3, 4, 5} s1 := s[2:3] fmt.Println(s1[1]) 复制代码
上面这个例子中, s1
的容量为3,长度为1,所以只能访问 s1
第一个元素 s1[0]
,访问 s1[1]
就会报错: panic: runtime error: index out of range
与切片的容量相关联的元素只能用于增长切片,在使用这部分元素前,必须将其合并到切片的长度里。
相较于数组,使用切片的好处在于,可以按需增长,类似于动态数组。Go提供了内置 append
函数,能够帮我们处理切片增长的一些列细节,我们只管使用就可以了。 函数原型:
func append(slice []Type, elems ...Type) []Type 复制代码
使用 append
函数,需要一个被操作的切片和一个(多个)追加值,返回一个相同数据类型的新切片。
s := []int{1, 2, 3, 4, 5} newS := s[2:4] newS = append(newS, 50) fmt.Println(s, newS) fmt.Println(&s[2] == &newS[0]) 复制代码
输出 :
[1 2 3 4 50] [3 4 50] true 复制代码
上面的例子中,截取获得一个长度为2,容量为3(可用容量为1)的新切片 newS
,通过 append
函数向切片 newS
追加一个元素50。 追加元素50之前:
追加元素50之后:
通过输出结果可以得出,新切片newS
与原切片
s
是共享底层数组的,当切片可用容量能够存下追加元素时,不会创建新的切片。
当切片可用容量存不下需要追加的元素时会发生呢?
s := []int{1, 2, 3, 4, 5, 6, 7, 8} s1 := s[2:4] fmt.Printf("before -> s=%v\n", s) fmt.Printf("before -> s1=%v\n", s1) fmt.Printf("before -> len=%d, cap=%d\n", len(s1), cap(s1)) fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0]) s1 = append(s1, 60, 70, 80, 90, 100, 110) fmt.Printf("after -> s=%v\n", s) fmt.Printf("after -> s1=%v\n", s1) fmt.Printf("after -> len=%d, cap=%d\n", len(s1), cap(s1)) fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0]) 复制代码
输出 :
before -> s=[1 2 3 4 5 6 7 8] before -> s1=[3 4] before -> len=2, cap=6 &s[2] == &s1[0] is true after -> s=[1 2 3 4 5 6 7 8] after -> s1=[3 4 60 70 80 90 100 110] after -> len=8, cap=12 &s[2] == &s1[0] is false 复制代码
追加元素60、70、80、90、100和110之前:
追加之后:
从结果可以看出,切片的底层数组没有足够的可用容量,append
函数会创建一个新的底层数组,将原数组的值复制到新数组里,再追加新的值,就不会影响原来的底层数组。
一般我们在创建新切片的时候,最好要让新切片的长度和容量一样,这样我们在追加操作的时候就会生成新的底层数组,和原有数组分离,就不会因为共用底层数组而引起奇怪问题,因为共用数组的时候修改内容,会影响多个切片。
append
函数会智能地增加底层数组的容量,目前的算法是:当数组容量 <=1024
时,会成倍地增加;当超过 1024
,增长因子变为1.25,也就是说每次会增加25%的容量。 Go提供了 ...
操作符,允许将一个切片追加到另一个切片上:
s := []int{1, 2,3,4,5} s1 := []int{6,7,8} s = append(s,s1...) fmt.Println(s,s1) 复制代码
输出:
[1 2 3 4 5 6 7 8] [6 7 8] 复制代码
迭代切片
使用 for
循环迭代切片,配合 len
函数使用:
s := []int{1, 2, 3, 4, 5} for i:=0;i<len(s) ;i++ { fmt.Printf("Index:%d,Value:%d\n",i,s[i]) } 复制代码
使用 for range
迭代切片:
s := []int{1, 2, 3, 4, 5} for i,v := range s { fmt.Printf("Index:%d,Value:%d\n",i,v) } // 使用‘_’可以忽略返回值 s := []int{1, 2, 3, 4, 5} for _,v := range s { fmt.Printf("Value:%d\n",v) } 复制代码
需要注意的是, range
返回的是切片元素的复制,而不是元素的引用。如果使用该值变量的地址作为指向每个元素的指针,就会造成错误。
s := []int{1, 2, 3, 4, 5} for i,v := range s { fmt.Printf("v:%d,v_addr:%p,elem_addr:%p\n",v,&v,&s[i]) } 复制代码
输出 :
v:1,v_addr:0xc000018058,elem_addr:0xc000016120 v:2,v_addr:0xc000018058,elem_addr:0xc000016128 v:3,v_addr:0xc000018058,elem_addr:0xc000016130 v:4,v_addr:0xc000018058,elem_addr:0xc000016138 v:5,v_addr:0xc000018058,elem_addr:0xc000016140 复制代码
可以看到, v
的地址总是相同的,因为迭代返回的变量在迭代过程中根据切片依次赋值的新变量。 好了,今天先讲到这里,下一节,我们再来讨论关于 Slice
更多的用法!
关注公众号「Golang来了」,获取最新文章!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Tornado
Michael Dory、Adam Parrish、Brendan Berg / O'Reilly Media / 2012-3-28 / USD 23.99
Tornado is a scalable, non-blocking web server and web application framework written in Python. It is also light-weight to deploy, fun to write for, and incredibly powerful. Tornado was written with p......一起来看看 《Introduction to Tornado》 这本书的介绍吧!