非懂不可的Slice(一)-- 就要学习Go语言

栏目: Go · 发布时间: 7年前

内容简介:切片是一种复合数据类型,与数组类似,存放相同数据类型的元素,但数组的大小是固定的,而切片的大小可变,可以按需自动改变大小。切片是基于底层数组实现的,是对数组的抽象。切片很小,只有三个字段的数据结构:如上图所示,一个长度为3、容量为5的整型切片的底层结构。使用内置函数创建空切片,形如:

切片是一种复合数据类型,与数组类似,存放相同数据类型的元素,但数组的大小是固定的,而切片的大小可变,可以按需自动改变大小。切片是基于底层数组实现的,是对数组的抽象。切片很小,只有三个字段的数据结构: 指向底层数组的指针 、能访问的元素个数(即 切片长度 )和允许增长到的元素个数(即 切片容量 )。

非懂不可的Slice(一)-- 就要学习 <a href='https://www.codercto.com/topics/6127.html'>Go</a> 语言

如上图所示,一个长度为3、容量为5的整型切片的底层结构。

声明与初始化

make()创建

使用内置函数创建空切片,形如:

s := make([]type, len, cap)  // len 长度,cap 容量
复制代码

也可以只指定 len ,那么切片的容量和长度是一样的。Go语言提供了内置函数 lencap 分别返回切片的长度和容量。

// 声明一个长度为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 索引的值。 ij 都是可选的, i 如果省略,默认是0, j 如果省略,默认是原切片或数组的长度。 ij 都不能超过这个长度值。

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])
复制代码

输出 你可能会有个疑问:截取获得的新切片的长度和容量怎么计算呢?我们当然可以使用内置函数 lencap 直接获得,如果明白了怎么计算的,我们处理问题就可以更得心应手。 对底层数组大小为 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 的长度和容量分别是 26注意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]
复制代码

这个例子说明,原切片和新切片是基于同一个底层数组的,所以当修改的时候,底层数组的值就会被改变,原切片的值也随之改变了。对于基于数组的切片也一样的。

非懂不可的Slice(一)-- 就要学习Go语言
我们可以看到,执行完切片动作之后,获得一个新切片,与原切片共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分。切片 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之前:

非懂不可的Slice(一)-- 就要学习Go语言

追加元素50之后:

非懂不可的Slice(一)-- 就要学习Go语言
通过输出结果可以得出,新切片 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之前:

非懂不可的Slice(一)-- 就要学习Go语言

追加之后:

非懂不可的Slice(一)-- 就要学习Go语言
从结果可以看出,切片的底层数组没有足够的可用容量, 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来了」,获取最新文章!

非懂不可的Slice(一)-- 就要学习Go语言

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro CSS Techniques

Pro CSS Techniques

Jeff Croft、Ian Lloyd、Dan Rubin / Apress / 2009-5-4 / GBP 31.49

Web Standards Creativity: Innovations in Web Design with CSS, DOM Scripting, and XHTML一起来看看 《Pro CSS Techniques》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具