思路引入:NBA每个球队的球员需要用一个数组保存,但是每个队由于交易问题,球员个数不确定,应该如何解决 —— 使用切片。
- 切片可以理解为动态数组,类似python列表中的.append(),切片长度可以变化的,数组长度是不可以变化的。
- 切片是数组的引用类型,在进行传递时遵守引用传递的机制。
- 遍历,访问,len(slice) 与数组都一样。
- 动态变化,随着元素增加动态增加。
- 基本语法: 与数组[n]xx不同,切片定义[]是没有值的: var slice1 []int , var slice2 []float64 …
切片基本使用案例:
package main
import "fmt"
func main() {
var intArray [5]int = [...]int{20,40,29,46,1}
//与python下标计算一样,取出来的值是(冒号后-冒号前),从下标为1的开始取,取三个
slice1 := intArray[3:4]
fmt.Println(slice1) //打印出slice切片的取值
fmt.Println(cap(slice1)) //查看切片容量,动态智能增加,不会说增加一个元素,cap + 1,分批次
}
切片在内存中分布:
-
切片是一个引用类型,并且是一个结构体 struct。
-
切片在内存中包含三个部分:
(1) 切片位置起始元素的内存地址,例如slice1 := intArray[3:4] 那么就是intArray中的: &intArray[3]。
(2) 切片长度,例如slice1 := intArray[3:4]长度就是 : 4 - 3= 1。
(3) 切片容量,如同前面所说的,动态智能增加的。 -
接第2条,var intArray [5]int = […]int{20,40,29,46,1} ,slice1 := intArray[1:4]
slice1 在内存中分布如下: &intArray[1]->(ptr) | 3->(len) | 4->(cap)
换成结构体写法:
*type slice1 struct {
ptr [3]int
len int
cap int
} -
由于是引用类型,因此对切片内的值进行修改,则会影响源数组的值。
package main
import "fmt"
func main() {
var intArray [5]int = [...]int{20,40,29,46,1}
slice1 := intArray[0:4]
slice1[0] = 200
fmt.Println(intArray) //这里发现intArray第一个值已经变为200
}
切片使用的三种方式:
-
使用创建好的数组作为切片对象:var intArray [5]int = […]int{20,40,29,46,1};slice1 := intArray[0:4]
Tips:
接对数组使用切片,元素值可以通过数组和切片维护,数组对程序员可见。 -
使用make()来创建切片,与new()分配一个内存空间一样,make()用来针对引用类型。
var slice2 []int = make([]int,,) //长度决定切片元素个数,cap >= len,元素全部为0Tips:
使用make()创建切片后,make也会创建一个数组,数组没有名称,程序员不可见,访问切片中的内存数据,只能通过slice作为入口来访问,元素数据是对外不可见的。 -
直接定义切片: var slice2 []int = []int {2,3,4} ,var slice2 []string = []string {“Durant”,“James”,“Kobe”}
切片注意事项:
4. 不可越界,0 ~ len(slicex)。
5. slice := intArray[n1:] //n1开始直到最后
slice := intArray[:n2] //n2到0所有值
slice := intArray[:] //数组所有值
-
定义完一个切片还不能使用,因为是空的,要么直接从数组拿值,要么后续添加。
-
切片可以迭代,切片的切片,slice2 := slice1[:],即便迭代,改变slice2中某个元素值,slice1和intArray都会改变,因为指向同一个内存空间的值!
-
切片的动态增加使用append()函数添加到切片末尾, append()为切片添加元素后需要一个变量接收
package main import "fmt" func main() { /* append()原理分析:底层新建一个数组,将新传递的值和slice的原值一并增加到一个新的数组里,让slice接收则指针指向新数组的首地址, 与此同时,slice原来指向的匿名数组就被当成垃圾回收了;让新的变量接收就不存在上述问题了,直接新的变量指向append后的数组。 */ var slice []int = []int{100,200,300} slice1 := append(slice,200,300,400) fmt.Println(slice1) //[100 200 300 200 300 400] append()本身并不改变源切片的值 fmt.Println(slice) //[100 200 300] slice = append(slice,slice...) // 自己追加自己,再把新的切片赋值给自己,也可以;slice...是固定写法 }
-
切片的copy(slice-dest,slice-src)内置函数,完成切片拷贝,两个参数都是slice类型,不能为其他类型
package main import "fmt" func main() { var slice1 []int = []int{1,2,3,4,5} var slice2 []int = []int{6,7,8,9,0,11,12,13} copy(slice1,slice2) //后面的参数会覆盖前面参数的slice对应位置的值 fmt.Println(slice1) fmt.Println(slice2) }
注意事项:
(1) 两个切片的数据类型必须一致!
(2) 源切片的长度如果超过目标切片长度,那么目标切片最终会取得源切片对应的值,源切片超出的部分会被丢失。
(3) 源切片的长度如果小于目标切片长度,那么目标切片会根据源切片的位置,改变自己的值,剩下未覆盖的部分不变。 -
string和slice的相关注意事项:
(1) string底层是一个byte数组,所以可以进行切片处理,内存中存在形式就是 字符串:“abcd” ----> a|b|c|d
(2) string本身不可变,不可以通过切片方式改变字符串中的某个值,例如把"abcd"改成 “accd”,切片做不到
(3) 如果想改变string中的某个值,可以先把string转换成切片[]byte或[]rune,修改后,再回转成stringpackage main import "fmt" func main() { /* 思路:string在Golang底层是一个byte数组 */ str := "Kurant" arr := []byte(str) //把str转换成一个byte数组,只能处理英文和数字,不能处理中文 arr[0] = 'D' //把首字母改成D,记得byte的值要用单引号'',不要用"" str = string(arr) //修改后把array转换成字符string fmt.Println(str) str_chs := "库兰特" //[]rune是按照字符处理数组的,可以处理3字节的中文 arr1 := []rune(str_chs) arr1[0] = '杜' str_chs = string(arr1) fmt.Println(str_chs) }
-
案例,用切片完成斐波那契数列的赋值
package main import "fmt" func main() { var n int fmt.Println("请输入斐波那契数列的长度值: ") fmt.Scanf("%d",&n) var slice []uint64 = make([]uint64,n) //定义一个切片接收变量,或者使用递归推导斐波那契函数 slice[0] = 1 slice[1] = 1 for i := 2 ; i < n ; i++ { slice[i] = slice[i-1] + slice[i-2] } fmt.Println(slice) }