1. 特点slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段以实现变长方案。切片切片是数组的一个引用因此切片是引用类型。但自身是结构体值拷贝传递。切片的长度可以改变因此切片是一个可变数组。切片的遍历方式和数组一样可以用len()求长度。表示可用元素的数量读写操作不能超过该限制。cap可以求出slice最大扩张容量不能超出数组限制。0 len(slice) len(array)。其中array是slice引用的数组。切片的定义var 变量名 []类型比如var str []string var arr []int。如果slice等于nil那么lencap结果都等于0。2. 切片源码切片对应的数据结构定义位于runtime/slice.go文件中其定义如下:type slice struct { array unsafe.Pointer len int cap int }array指针指向引用数组对应位置。len可用元素个数。cap容量可放元素个数。我们在进行切片赋值传参截断时其实是复制一个slice结构体只不过底层数组是同一个。这就导致了无论是在复制的切片中修改值还是修改形参切片值都会修改到原来的切片和引用的数组。总的来说切边底层有一个数组(make创建的切片底层也会先创建一个数组)切片是数组的引用。3. 创建切片4. 切片初始化使用s[low : high : max]时要求为high max len(原数组/切片)如果max不填则为数组长度。package main import fmt //全局变量 var arr [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} var slice0 []int arr[2:8] var slice1 []int arr[0:6] //可以简写为arr[:6] var slice2 []int arr[5:10] //可以简写为arr[5:] var slice3 []int arr[0:len(arr)] //可以简写为arr[:] var slice4 []int arr[0 : len(arr)-1] //去掉最后一个元素 func main() { fmt.Printf(全局变量arr %v\n, arr) fmt.Printf(全局变量slice0 %v\n, slice0) fmt.Printf(全局变量slice1 %v\n, slice1) fmt.Printf(全局变量slice2 %v\n, slice2) fmt.Printf(全局变量slice3 %v\n, slice3) fmt.Printf(全局变量slice4 %v\n, slice4) fmt.Println(--------------------------) arr2 : [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0} slice5 : arr2[2:8] slice6 : arr2[0:6] //可以简写为arr[:6] slice7 : arr2[5:10] //可以简写为arr[5:] slice8 : arr2[0:len(arr)] //可以简写为arr[:] slice9 : arr2[:len(arr)-1] //去掉最后一个元素 fmt.Printf(局部变量arr %v\n, arr2) fmt.Printf(局部变量slice5 %v\n, slice5) fmt.Printf(局部变量slice6 %v\n, slice6) fmt.Printf(局部变量slice7 %v\n, slice7) fmt.Printf(局部变量slice8 %v\n, slice8) fmt.Printf(局部变量slice9 %v\n, slice9) }5. 通过make来创建切片var slice []type make([]type, len) var slice []type make([]type, len, cap) slice : make([]type, len) slice : make([]type, len, cap)切片的内存布局读写操作实际目标是底层数组只需要注意索引号的差别。package main import fmt func main() { data : [...]int{0, 1, 2, 3, 4, 5} s : data[2:4] s[0] 100 s[1] 200 fmt.Println(s) fmt.Println(data) }可直接创建slice对象自动分配底层数组package main import fmt func main() { var s1 []int []int{0, 1, 2, 3, 8: 100} //通过初始化表达式构造可使用索引号 fmt.Println(s1, len(s1), cap(s1)) s2 : make([]int, 6, 8) //使用make创建指定len和cap fmt.Println(s2, len(s2), cap(s2)) s3 : make([]int, 6) //使用make创建省略cap相当于caplen fmt.Println(s3, len(s3), cap(s3)) }使用make动态创建slice避免了数组必须用常量的麻烦。还可以用指针直接访问底层数组退化成普通数组操作。package main import fmt func main() { s : []int{1, 2, 3, 4} p : s[1] //获得底层数组元素指针 *p 100 fmt.Println(s) }至于[][]T是指元素类型为[]Tpackage main import fmt func main() { data : [][]int{ []int{1, 2, 3}, []int{10, 20, 30, 40}, []int{100, 200}, } fmt.Println(data) }可以直接修改struct array/slice成员package main import fmt func main() { data : [5]struct { x int }{} s : data[:] s[1].x 10 s[2].x 20 fmt.Println(data) fmt.Printf(%p, %p\n, data, data[0]) }6. 用append内置函数操作切片(切片追加)append向slice尾部添加数据返回新的slice对象。package main import ( fmt ) func main() { var s []int //s是nil没有分配内存 fmt.Println(s) s append(s, 1) //append会先为s分配内存再存放数据 fmt.Println(s) var a []int []int{1, 2, 3} fmt.Printf(slice a: %v\n, a) b : []int{4, 5, 6} fmt.Printf(slice b: %v\n, b) //append向slice尾部添加数据 c : append(a, b...) fmt.Printf(slice c: %v\n, c) d : append(a, 10) fmt.Printf(slice d: %v\n, d) e : append(a, 100, 200, 300) fmt.Printf(slice e: %v\n, e) //append返回的新的slice对象 fmt.Printf(a:%p, c:%p, d:%p, e:%p, a, c, d, e) }8. slice扩容超出元slice.cap限制就会重新分配底层数组即便原数组并未填满。从下面现象可以看出当数据没有超出slice的cap时如果引用存在的数组变量(不是make创建的)底层数组使用的是存在的数组变量。修改切片的值就会修改数组的值。当数据超出slice的cap时会重新分配底层数组修改切片的值不会修改存在数组变量的值而是修改切片底层数组的值。切片基于数组创建后一旦触发扩容就一定会重新分配新的底层数组空间扩容后的切片不再引用原数组。为什么扩容后必须换数组你的源数组是[10]int长度固定为 10它的内存空间是「静态分配」的无法扩展。Go 只能新建一块更大的内存才能容纳更多元素。只有「容量不足」才会换数组比如你创建切片sli : arr[0:5]cap10此时 append 5 个元素len10 ≤ cap10仍会复用原数组但再 append 1 个元素len11 cap10就会触发扩容切换到新数组。package main import fmt func main() { var data [...]int{1, 2, 3, 4, 10: 0} s : data[:2:3] fmt.Printf(data:%p, data[0]:%p\n, data, data[0]) //data:0xc0000103f0, data[0]:0xc0000103f0 //slice中数据保存的是数组对应下标的地址使用索引访问相当于对指针解引用在取地址实际取的是数组中的地址 fmt.Printf(s:%p, s:%p, s[0]:%p\n, s, s, s[0]) //s:0xc0000103f0, s:0xc000008048, s[0]:0xc0000103f0 //未扩容前修改值 s[0] 11 fmt.Println(s, data) s append(s, 100, 200) fmt.Println(s, data) fmt.Printf(s[0]:%p, data[0]:%p\n, s[0], data[0]) //扩容后修改值 s[0] 10 fmt.Println(s, data) }slice中cap重新分配规律从下面输出可以看出slice扩容通常是以两倍原来容量重新分配底层数组。建议一次性分配足够大的空间以减少内存分配和数据复制的开销或者初始化足够长的len属性改用索引号进行操作。及时释放不再使用的slice对象避免持有过期数组造成GC无法回收。package main import fmt func main() { s : make([]int, 0, 2) c : cap(s) for i : 0; i 50; i { s append(s, i) if c cap(s) { fmt.Printf(cap: %d-%d\n, c, cap(s)) c cap(s) } } }9. 切片拷贝函数copy在两个slice间复制数据复制长度以len小的为准。两个slice可指向同一底层数组允许元素区间重叠(本来创建切片不同的slice也可以指向同一个底层数组)。应及时将所需数据copy到较小的slice以便释放超大号底层数组内存。10. slice遍历slice遍历实际和数组遍历一样。package main import fmt func main() { data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 : data[:] for index, value : range s1 { fmt.Printf(s1[%d] %d\n, index, value) } fmt.Println(-----------------------------) for i : 0; i len(s1); i { fmt.Printf(s1[%d] %d\n, i, s1[i]) } }11. 切片调整大小由于底层数组相同且数组空间连续。所以可以像下面赋值。12. 数组和切片的内存分布底层数组不一定要声明出来比如make的切片或者 s : []int{1, 2, 3}13. 字符串和切片string底层就是一个byte的数组因此也可以进行切片操作。package main import fmt func main() { str : hello world s : str[:5] fmt.Println(s) s1 : str[6:] fmt.Println(s1) }修改字符串string本身是不可修改的要修改string需要先转成[]byte或[]rune进行修改再转成string。英文字符串package main import fmt func main() { str : hello world //字符串不能修改 // s1 : str[:8] // s1[6] G // str[6] G //转成[]byte bt : []byte(str) //中文字符需要用[]rune(str) //修改[]byte bt[6] G bt bt[:8] bt append(bt, !) str string(bt) fmt.Println(str) }含中文字符串package main import fmt func main() { str : 你好 世界 hello world //字符串不能修改 // s1 : str[:8] // s1[6] G // str[6] G rn : []rune(str) //修改[]byte rn[3] 够 rn[4] 浪 rn rn[:12] rn append(rn, 好) //再转回字符串 str string(rn) fmt.Println(str) }数组或切片转字符串strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), []), , ,, -1)golang slice data[:6:8]两个冒号的理解对于data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}常规slicedata[6:8]从data数组索引6到索引7长度len为2最大可扩充长度cap为4(slice底层数组为data指针指向索引6位置cap从索引6位置到data最后即6-9所以cap为4)。另一种写法data[:6:8]每一个数字前面都有冒号slice内容为data索引从0到6长度len为6最大扩充项cap设置为8。a[x:y:z]切片内容[x:y]前闭后闭切片长度y-x切片容量z-xpackage main import fmt func main() { data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 : data[6:8] fmt.Println(len(s1), cap(s1)) //s2 : data[6: :8] 中间必须要有数字 s2 : data[:6:8] fmt.Println(len(s2), cap(s2)) fmt.Println(s2) }