切片(Slice)是Go语言类型系统中的一种基本的数据类型,其功能有点类似于C++中的vector,都是围绕动态数组概念构建的,可以按需自动增长和缩小。但是切片本身并不是动态数组,而是对底层数组的抽象。
一、数组or切片 如何选择
Go语言中数组和C/C++中的数组有什么区别呢?首先,Go语言中的数组是值类型,C语言数组变量会隐式变成指针;所以Go语言中数组变量传参或者赋值都会复制整个数组的数据,而C/C++语言数组传参时数组变量退化成指针。
数组赋值和作为参数传递 测试代码:
package main
import "fmt"
func main(){
array1 := [5]int{1,2,3,4,5}
var array2 [5]int
array2 = array1
fmt.Printf("array1 addr:%p , array1:%v\n",&array1,array1)
fmt.Printf("array2 addr:%p , array2:%v\n",&array2,array2)
sendarg(array1)
}
func sendarg(array [5]int){
fmt.Printf("sendarg addr:%p , %v\n",&array,array)
}
测试结果:
array1 addr:0xc04200a210 , array1:[1 2 3 4 5]
array2 addr:0xc04200a240 , array2:[1 2 3 4 5]
sendarg addr:0xc04200a2d0 , [1 2 3 4 5]
可以看到第二行输出的赋值和第三行输出的作为函数参数,是三个不同的地址,所以都对数组进行了复制。这一特性会有什么影响呢?
如果每次传参直接用数组,如果数组太大,而Go语言又需要赋值整个数组,这势必会导致大量的内存消耗;此时貌似我们可以用数组指针传参。
用数组指针传参 测试代码:
package main
import "fmt"
func main(){
array1 := [5]int{1,2,3,4,5}
fmt.Printf("array1 addr:%p , array1:%v\n",&array1,array1)
testPoint(&array1)
slice1 := array1[:]
fmt.Printf("slice1 addr:%p , slice1:%v\n",&slice1,slice1)
testArray(&slice1)
}
func testPoint(array *[5]int){
fmt.Printf("数组指针作为参数 addr:%p , %v\n",array,*array)
(*array)[0]*=10
}
func testArray(array *[]int){
fmt.Printf("切片作为参数 addr:%p , %v\n",array,*array)
(*array)[0]*=10
}
测试结果:
array1 addr:0xc042068030 , array1:[1 2 3 4 5]
数组指针作为参数 addr:0xc042068030 , [1 2 3 4 5]
slice1 addr:0xc0420463c0 , slice1:[10 2 3 4 5]
切片作为参数 addr:0xc0420463c0 , [10 2 3 4 5]
可以看出来,第一行和第二行结果输出的地址是同一个,即用数组指针作为参数传递只是传递了一个指针,确实能减小内存的消耗。但是,此时如果我们使用指针调用的函数内改变了数组的值,则原数组的值也会因此改变,所以要确定在函数内改变数组的值是不是符不符合你的想法。
而我们从第三、四行输出结果可以看出,如果切片作为参数传递,也是一样的结果,要注意切片被调用函数改变的情况。
第二、切片的底层实现
Go语言中切片数据结构在源码包src的 ./runtime/slice.go里面。
type slice struct {
array unsafe.Pointer
len int
cap int
}
unsafe包提供了一些跳过go语言类型安全限制的操作,而unsafe.Pointer的Pointer类型用于表示任意类型的指针。具体见https://studygolang.com/pkgdoc
参考:
https://www.jianshu.com/p/030aba2bff41