http://blog.wuxu92.com/array-and-slice-in-golang
https://www.zhihu.com/question/66673454/answer/244731263
先搞清楚Array和Slice的定义,再谈他们的差异,以及如何区分。
Array 简单数组:是一组同类型元素的序列,序列长度也是数组定义的一部分。语法为:
Array := [ArrayLength] ElementType
不同长度的数组代表不同的数据类型:
arr1:=[3]string{}
arr2:=[6]string{}
fmt.Printf("type of arr1 is %s, the kind is %s\n",reflect.TypeOf(arr1),reflect.TypeOf(arr1).Kind())
fmt.Printf("type of arr2 is %s, the kind is %s\n",reflect.TypeOf(arr2),reflect.TypeOf(arr2).Kind())
//output
//type of arr1 is [3]string, the kind is array
//type of arr2 is [6]string, the kind is array
同时,Array在定义时,便给内部元素赋值为类型默认值。
arr :=[4]int
arr内存结构如下,已经包含四个元素,其默认值为0。
当在定义Array时,如果无法确定长度,则可以使用"...",来由实际元素个数确定数组长度:
b := [...]string{"Penn", "Teller"}
此时,数组b则由编译器检查其元素个数,确认其长度为2。
b := [2]string{"Penn", "Teller"}
实际两者等价。
Array一个重要的特性是:长度不可变。
那么如何满足可变长度的操作呢?最简单方式便使用Slice
Slice切片:一种提供丰富操作功能的元素序列,是对简单数组的高级抽象,是简单数组的强化版。
实际切片内部也是对简单数据的引用,再提供高效的方式操作数组。其语法定义是:
SliceT := []ElementType
是不需要特别指定长度的,内部将包含容量、元素长度信息。
上图中,定义一个切片,数据来源于数组Array_a的3个元素,构造一个新的切片。而切片的元素长度为3,切片容量为8。
Array和Slice两者的差异
两者最大的差异是数组长度一旦确定便不可修改,无法变长,无法改短。而切片则通过伸缩容量。且数组是值类型,把一个数组赋予给另一个数组时是发生值拷贝,而切片是指针类型,拷贝的是指针。
下面通过一个实例来说明:
代码如下图,(1)是定义一个[3]int类型数组,依次赋值为1,2,3。(2)将数组a赋值给b,将发生值拷贝。(3)给数组a的下标2的元素赋值为4,这并不会影响数组b的内容。(4)打印结果,得以说明情况。
在看另一段代码:(1)不指定长度,(2)打印结果,显示有对a的修改影响了b的内容。
为何一个细微的变化有如此差异呢?这个便是Array和Slice定义时语法上的细微差异:
在括号中定义一个长度值,是编译器区别变量a数据类型的唯一依据,带长度值便是数组,不带便是切片。
再回到题主的疑问,下面变量如何区分:
var a1 []int ---> 无长度定义,是Slice
var a2 [2]int ---> 有长度定义,是Array
var a3 [3]*int---> 有长度定义,是Array
var a4 [4][3]int ---> 有长度定义,是Array,只不过是一个二维数组。
golang内置对切片(slice)的支持,但是golang的类型系统对数组(array)的使用并没有动态/脚本语言那么方便,在使用中经常需要使用切片和数组的一些操作。
首先对于数组要注意数组的数据类型是存储的元素的类型加数组的长度;也就是说[2]int
和[3]int
是两种不同的类型。而切片的类型只和数据的类型有关。
声明一个数组和一个切片是不同的。
var arr [5]int arr := [5]int{1,2,3,4,5 } var sli []int sli := []int{1,2,3,4,5} |
数组的类型是一维的,可以构造数组的数组来实现多维数组。
一般切片使用make方法创建,make方法的签名如下
func make([]T, len, cap) []T
|
T是声明的切片保存的数据的类型。其中len表示切片的长度,cap表示capacity。一般使用时省略cap参数,默认和len参数相同。capacity和length在切片中是可能不一样的。
var s []int s = make([]int, 5) // equals to s := []int{0,0,0,0,0} |
切片类型重要的操作就是“切片”,使用一个半开的区间,使用两个索引指定要切片的范围([lo, hi]两个索引是前闭后开的)。
s := []byte{'a', 'd', 's', 'f', 'g', 'h'} // s[1:4] == []byte{'d', 's', 'f' } |
使用时要注意前闭后开的性质,切片后的长度为hi-lo
。
特别需要注意的是: 切片操作返回的切片与原切片共享存储。切片操作不不会复制原切片的数据,而是将新的切片的指针指向原切片的某个元素。示意如下
s[:] == s as := s[2:4] // 对于上面生命的s // len(as) = hi - lo = 4-2 =2 // cap(as) == len(s)-lo = 5-2 = 3 |
这样可以理解切片的len和cap的区别了。
可以使用“切片”操作把数组转换为切片
arr := [3]int{1,2,3} sli := arr[:] |
也就是说可以对数组做“切片”操作。
切片不同于数组的地方在于切片的copy和append方法。
copy方法的签名为:func copy(dst, src []T) int
; copy方法还可以处理共享相同底层数组的切片,处理dst和src的重叠问题(比如上面的s和as)。当dst和src的长度不一样时,只会拷贝较短的部分。理解这一部分这也是很重要的
sli := []int{1,2,3,4,5} t := []int{11,21,31,41,5,6,7,8,9} copy(sli, t) // sli=[11,21,31,41,5] // copy(t, sli) // t=[1,2,3,4,5,6,7,8,9] |
append方法用于对切片追加元素。append方法的签名为: func append(s []T, x ...T) []T
; 方法想s的结尾追加元素x,如果len小于cap则直接追加,如果capacity不够则会自动扩大切片。
append可以用来追加元素也可以用来将一个切片追加到另一个切片后面。这里使用...
操作符(expand operator)
append(t, 100, 200) append(t, sli...) |
需要注意的是,append会返回一个新的切片而不是修改原来的切片(试验了一下,append返回的切片不是共享存储),可以通过append方法的签名看出。
切片共享存储可能存在问题是,如果一个很大的切片“切”出一小片保存在一个新切片,而这个大切片这已经不需要了,但是由于小切片保留了这些引用,所以GC Collector还不能回收这一个大切片。如果真的遇到这样场景推荐罢这小块切片复制出来。
参考:
- http://blog.golang.org/go-slices-usage-and-internals
- https://gobyexample.com/slices
- https://gobyexample.com/slices
- https://golang.org/doc/effective_go.html#arrays
How to convert slice to fixed size array?
slice := []byte("abcdefgh")
var arr [4]byte
copy(arr[:], slice[:4])
fmt.Println(arr)