数组和切片有什么区别?
golang 中数组是固定长度的,不能动态扩容,声明方式为:
var array [10]int
array := [10]int{0}
切片是对数组的抽象,长度是不固定的,可以追加元素,切片不是数组,是描述的一块数组。
ints := make([]int,2,5)
切片可以用append()追加元素,当cap不足时进行动态扩容
2.拷贝大切片一定比小切片代价大吗?
不会,切片本质内部结构如下
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
切片中的data是指向切片底层数组的指针,这是切片的存储空间,len是切片的长度,cap是切片的容量,将一个切片变量分配给另一个变量只会复制三个机器字,如果发生拷贝,就是拷贝上面三个字段。
3.切片的深浅拷贝
使用 “=”操作符或者[:]下标的方式复制切片,是浅拷贝,复制的对象与源对象指向同一个地址
使用go的内置函数copy()进行切片拷贝,是深拷贝,会重新开辟一段存储空间来保存,互不影响。
4.0切片,空切片,nil切片
0切片:
slice := make([]int,5) // 0 0 0 0 0
slice := make([]*int,5) // nil nil nil nil nil
我们把切片内部数组的元素都是零值或者底层数组的内容就全是nil的切片叫做0切片,使用make创建的,长度、容量都不为0的就是零值切片
nil切片:
var slice []int
var slice = *new([]int)
长度和容量都为0,并且和nil比较结果为true,使用直接创建切片或者new创建切片的方式都可以创建nil切片
空切片:
var slice = []int{}
var slice = make([]int, 0)
空切片长度和容量也都为0,但是和nil比较结果为false,因为所有的空切片的数据指针都指向同一个地址,0xc42003bda0,使用字面量,make 可以创建空切片。
5.切片的扩容策略
1.预估扩容规则:
if oldCap *2 < cap{
newCap = cap
} else if oldLen<1024 {
newCap = 2*OldCap
} else {
newCap = oldCap *1.25
}
2.计算实际占用字节数
cap*元素大小,得到字节数
3.匹配内存规格
6.参数传递切片和切片指针有什么区别?
当切片作为参数传递时,其实就是一个结构体的传递,因为Go
语言参数传递只有值传递,传递一个切片就会浅拷贝原切片,但因为底层数据的地址没有变,所以在函数内对切片的修改,也将会影响到函数外的切片。
但也有特例:
当直接传递切片时,如果指向底层数组的指针被覆盖或者修改(copy,重分配,append扩容),此时函数内部对数据的修改将不再影响到外部的切片,代表长度的len和容量cap也不会被修改。
参数传递切片指针:如果想修改切片中元素的值,并且更改切片的容量和底层数组,则应该指针传递。
代码示例:
package main
import "fmt"
func appendSlice(s []string) {
s = append(s, "test3")
fmt.Println("out slice: ", s)
fmt.Printf("out addr:%p", &s)
}
func appendSlice1(s *[]int) {
*s = append(*s, 999)
fmt.Println("out slice: ", s)
fmt.Printf("out addr:%p\n", &s)
}
func main() {
s := []string{"test1", "test2"}
appendSlice(s)
fmt.Println("inner slice: ", s)
fmt.Printf("inner addr:%p\n", &s)
slice := make([]int, 1)
fmt.Printf("inner origin int addr:%p\n", &slice)
slice = append(slice, 1)
fmt.Println("inner int slice:", slice)
fmt.Printf("inner append 1 addr:%p\n", &slice)
appendSlice1(&slice)
fmt.Println("inner slice int: ", slice)
fmt.Printf("inner int appendslice1 after addr:%p\n", &slice)
}
out slice: [test1 test2 test3]
out addr:0xc000092078inner slice: [test1 test2]
inner addr:0xc000092060
inner origin int addr:0xc0000920c0
inner int slice: [0 1]
inner append 1 addr:0xc0000920c0
out slice: &[0 1 999]
out addr:0xc0000ca020
inner slice int: [0 1 999]
inner int appendslice1 after addr:0xc0000920c0
7.range遍历切片有什么要注意的?
go语言提供了range关键字用于for循环中迭代数组array、切片slice、通道channel、集合map的元素,有两种使用方式:
for k,v := range _ { }
for k := range _ { }
第一种是遍历下表和对应值,第二种是只遍历下标,使用range遍历切片时会先拷贝一份,然后再辨别能力拷贝数据。
func main() {
test := []int{1, 2, 3, 4, 5}
fmt.Printf("test origin :%v,addr:%p\n", test, &test)
for k, v := range test {
if v < 3 {
v = 9
}
fmt.Printf("k:%v,v:%v,addr:%p,vaddr:%p\n", k, v, &test, &v)
}
fmt.Println(test)
for k, v := range test {
if v < 3 {
test[k] = 9
}
fmt.Printf("k:%v,v:%v,addr:%p,vaddr:%p\n", k, v, &test, &v)
}
fmt.Println(test)
}
output:
test origin :[1 2 3 4 5],addr:0xc00000e3c0
k:0,v:9,addr:0xc00000e3c0,vaddr:0xc00001a0b8
k:1,v:9,addr:0xc00000e3c0,vaddr:0xc00001a0b8
k:2,v:3,addr:0xc00000e3c0,vaddr:0xc00001a0b8
k:3,v:4,addr:0xc00000e3c0,vaddr:0xc00001a0b8
k:4,v:5,addr:0xc00000e3c0,vaddr:0xc00001a0b8
[1 2 3 4 5]
k:0,v:1,addr:0xc00000e3c0,vaddr:0xc00001a0f0
k:1,v:2,addr:0xc00000e3c0,vaddr:0xc00001a0f0
k:2,v:3,addr:0xc00000e3c0,vaddr:0xc00001a0f0
k:3,v:4,addr:0xc00000e3c0,vaddr:0xc00001a0f0
k:4,v:5,addr:0xc00000e3c0,vaddr:0xc00001a0f0
[9 9 3 4 5]
因为使用range遍历切片,变量v是拷贝切片中的数据,修改拷贝数据不会对原切片有影响。