1、从数组到slice
2、slice 结构
3、切片作为参数
1、从数组到slice
- 先用数组来模拟slice
// 数组的容量为20,类似于slice的cap
var vals [20]int
// 实际在数组中添加了5个元素,类似于slice的length,当前为5
for i := 0; i < 5; i++ {
vals[i] = i * i
}
subsetLen := 5
fmt.Println("The subset of our array has a length of:", subsetLen)
// 数组模拟slice再添加一个元素,此时length为6
vals[subsetLen] = 123
subsetLen++
fmt.Println("The subset of our array has a length of:", subsetLen)
- 上面代码转换为slice
// 类似于数组的 var vals [20]int
var := make([]int, 0, 20)
// 添加了5个元素,slice 的len 变为5
for i := 0; i < 5; i++ {
vars = append(vals, i * i)
}
fmt.Println("The slice length of: ", len(vals)
// slice再添加一个元素,此时len为6
vals = append(vals, 123)
fmt.Println("The slice has a length of:", len(vals)
2、slice 结构
slice 结构
- 一个数组指针:指向底层数组
- 一个元素的个数 length:当前底层数组中加入了多少个元素
- 一个容量cap:底层数组的容量或是分配的内存大小
当length超过容量时,将导致切片底层数组重新被分配,分配后cap的大小将变为原来cap的两倍
// 定义一个切片,默认内置5个元素0,此时cap也是5
var sliceTwo = make([]int, 5)
fmt.Println("init slice info ", unsafe.Pointer(&sliceTwo[0]),
" len: ", len(sliceTwo),
" cap: ", cap(sliceTwo),
" content: ", sliceTwo)
// slice中cap已经被占满,再添加一个元素将引起slice
// 底层数组重新分配
sliceTwo = append(sliceTwo, 1)
fmt.Print("first append, slice info: ",
unsafe.Pointer(&sliceTwo[0]),
" len: ", len(sliceTwo),
" cap: ", cap(sliceTwo),
" content: ", sliceTwo)
// 再添加一个,由于cap够,所以不应该重新分配底层数组
sliceTwo = append(sliceTwo, 1)
fmt.Print("second append, slice info: ",
unsafe.Pointer(&sliceTwo[0]),
" len: ", len(sliceTwo),
" cap: ", cap(sliceTwo),
" content: ", sliceTwo)
输入如下
//初始地址:0xc42001a120, cap=5, len=5
init slice info: 0xc42001a120 len: 5 cap: 5 content [0 0 0 0 0]
//第一次添加元素:底层数组地址变化为:0xc4200180f0,len:6, cap翻倍为10
first append, slice info: 0xc4200180f0 len: 6 cap: 10 content: [0 0 0 0 0 1]
//第二次添加,底层数组地址未变 0xc4200180f0, len=7, cap=10
second append, slice info: 0xc4200180f0 len: 7 cap: 10 content: [0 0 0 0 0 1 1]
注意:make([]int, len, cap), 未指定cap时,len与cap相同,len的含义时在分配cap容量的数组之后,添加len个零值元素,所以后续再添加元素时要从len的位置开始添加,而不是从0位置开始
3、切片作为参数
slice作为参数,函数不进行append操作,此时可以改变现有的值
slice作为参数,函数进行添加append参数,但是并没有超过原来的cap,此时函数内部append后新的slice的长度length属性不影响外面的length,原来是多少就是多少,新的length只在函数内部生效
slice作为参数,进行append,同时cap发生变化时,函数内部实际操作的是另一个切片,完全和外面的切片无关
可以概括为,切片作为参数时,函数内部只有操作该切片已有length内的元素时会影响到原来的切片,其它不受影响
例1、操作切片的值
func reverseOne(s []int) {
for i, j := 0, len(s) - 1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
fmt.Println("func reverse ",
" addr=", unsafe.Pointer(&s[0]),
" len=", len(s),
" cap=", cap(s),
" content=", s)
}
func main() {
var sliceOne []int
for i := 1; i <= 3; i++ {
sliceOne = append(sliceOne, i)
}
fmt.Println("init slice ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
"content=", sliceOne)
fmt.Println()
reverseOne(sliceOne)
fmt.Println("after reverse ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
" content=", sliceOne)
fmt.Println()
}
输出:
init slice addr= 0xc420088000 len= 3 cap= 4 content= [1 2 3]
//传入函数之后,实际操作切片底层数组地址处的值
func reverse addr= 0xc420088000 len= 3 cap= 4 content= [3 2 1]
// 实现数组翻转,因为函数操作了地址处的值
after reverse addr= 0xc420088000 len= 3 cap= 4 content= [3 2 1]
例2、函数中不改变cap,但是改变length影响不了原来切片的length,但是可以操作已有值
调用append一个新的slice创建,新的slice有新的length属性,但它不是指针,但是底层还是只想同一块数组地址
func reverseOne(s []int) {
s = append(s, 999)
for i, j := 0, len(s) - 1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
fmt.Println("func reverse ",
" addr=", unsafe.Pointer(&s[0]),
" len=", len(s),
" cap=", cap(s),
" content=", s)
}
func main() {
var sliceOne []int
for i := 1; i <= 3; i++ {
sliceOne = append(sliceOne, i)
}
fmt.Println("init slice ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
"content=", sliceOne)
fmt.Println()
reverseOne(sliceOne)
fmt.Println("after reverse ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
" content=", sliceOne)
fmt.Println()
}
输出:
init slice addr= 0xc420014100 len= 3 cap= 4 content= [1 2 3]
// 函数内部改变了原来切片0位置处的值
func reverse addr= 0xc420014100 len= 4 cap= 4 content= [999 3 2 1]
// 原切片0位置处的值发生变化,但长度length没有变
after reverse addr= 0xc420014100 len= 3 cap= 4 content= [999 3 2]
例3、函数中若重新分配了切片的cap,那么函数中append新创建的切片只在函数内部有效,不影响作为参数传进来时外部的切片
调用append一个新的slice创建,新的slice有新的length属性,但它不是指针,但是底层还是只想同一块数组地址
func reverseOne(s []int) {
// 超过了cap,重新分配底层数组
s = append(s, 999, 1000, 1001)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
fmt.Println("func reverse ",
" addr=", unsafe.Pointer(&s[0]),
" len=", len(s),
" cap=", cap(s),
" content=", s)
}
func main() {
var sliceOne []int
for i := 1; i <= 3; i++ {
sliceOne = append(sliceOne, i)
}
fmt.Println("init slice ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
"content=", sliceOne)
fmt.Println()
reverseOne(sliceOne)
fmt.Println("after reverse ",
" addr=", unsafe.Pointer(&sliceOne[0]),
" len=", len(sliceOne),
" cap=", cap(sliceOne),
" content=", sliceOne)
fmt.Println()
}
输出:
init slice addr= 0xc420014100 len= 3 cap= 4 content= [1 2 3]
// 底层数组及cap重新分配了
func reverse addr= 0xc420016080 len= 6 cap= 8 content= [1001 1000 999 3 2 1]
//没有影响外面的切片
after reverse addr= 0xc420014100 len= 3 cap= 4 content= [1 2 3]