GOLANG切片相关知识

数组和切片有什么区别?

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是拷贝切片中的数据,修改拷贝数据不会对原切片有影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值