本篇会详细讲解go语言中的array和slice,和平时开发中使用时须要注意的地方,以避免入坑。数组
Go语言中array是一组定长的同类型数据集合,而且是连续分配内存空间的。bash
var arr [3]int复制代码
数组声明后,他包含的类型和长度都是不可变的.若是你须要更多的元素,你只能从新建立一个足够长的数组,并把原来数组的值copy过来。app
在Go语言中,初始化一个变量后,默认把变量赋值为指定类型的zero值,如string 的zero值为"" number类型的zero值为0.数组也是同样的,声明完一个数组后,数组中的每个元素都被初始化为相应的zero值。如上面的声明是一个长度为5的int类型数组。数组中的每个元素都初始化为int类型的zero值 0函数
可使用array的字面量来快速建立和初始化一个数组,array的字面量容许你设置array的长度和array中元素的值性能
arr := [3]{1, 2, 3}复制代码
若是[]中用...来代替具体的长度值,go会根据后面初始化的元素长度来计算array的长度ui
arr := [...]{1, 2, 3, 4}复制代码
若是你想只给某些元素赋值,能够这样写spa
arr := [5]int {1: 5, 3: 200}复制代码
上面的语法是建立了一个长度为5的array,并把index为1的元素赋值为0,index为3的元素赋值为200,其余没有初始化的元素设置为他们的zero值3d
声明一个包含有5个整数指针类型的数组,咱们能够在初始化时给相应位置的元素默认值。下面是给索引为0的元素一个新建的的int类型指针(默认为0),给索引为1的元素指向值v的地址,剩下的没有指定默认值的元素为指针的zero值也就是nil指针
var v int = 6
array := [5]*int{0: new(int), 1: &v}
fmt.Println(len(array))
fmt.Println(*array[0])
fmt.Println(*array[1])
v2 := 7
array[2] = &v2
fmt.Println("------------------")
for i, v := range array {
fmt.Printf("index %d, address %v value is ", i, v)
if v != nil {
fmt.Print(*v)
} else {
fmt.Print("nil")
}
fmt.Println(" ")
}复制代码
好比我个建立一个100万长度的int类型数组,在64位机上须要在内存上占用8M的空间,把他作为一个参数传递到一个方法内,go会复制这个数组,这将致使性能的降低。 code
package main
import (
"fmt"
)
const size int = 1000*1000
func sum(array [size]int) float64 {
total := 0.0
for _, v := range array {
total += float64(v)
}
return total
}
func main() {
var arr [size]int
fmt.Println(sum(arr))
}复制代码
固然go也提供了其余的方式,能够用指向数组的指针作为方法的参数,这样在传参的时候会传递array的地址,只须要复制8个字节,
package main
import (
"fmt"
)
const size int = 1000*1000
func sum(array *[size]int) float64 {
total := 0.0
for _, v := range array {
total += float64(v)
}
return total
}
func main() {
var arr [size]int
fmt.Println(sum(&arr))
}复制代码
slice能够被认为动态数组,在内存中也是连续分配的。他能够动态的调整长度,能够经过内置的方法append来自动的增加slice长度;也能够经过再次切片来减小slice的长度。
slice的内部结构有3个字段,分别是维护的底层数组的指针,长度(元素个数)和容量(元素可增加个数,不足时会增加),下面咱们定义一个有2个长度,容量为5的slice
func main() {
s := make([]int, 2, 5)
fmt.Println("len: ", len(s))
fmt.Println("cap: ", cap(s))
s = append(s, 2)
fmt.Println("--------")
fmt.Println("len: ", len(s))
fmt.Println("cap: ", cap(s))
fmt.Println("--------")
s[0] = 12
for _, v := range s {
fmt.Println(v)
}
}复制代码
初始化slice后,他的长度为2,也就是元素个数为2,由于咱们没有给任何一个元素赋值,因此为int的zero值,也就是0.能够用len和cap看一下这个slice的长度和容量。
用append给这个slice添加新值,返回一个新的slice,若是容量不够时,go会自动增长容易量,小于一1000个长度时成倍的增加,大于1000个长度时会以1.25或者25%的位数增加。
上面的代码执行完后,slice的结构以下
建立和初始化一个slice有几种不一样的方式,下面我会一一介绍
slice1 := make([]int, 3)
fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)
slice1 = append(slice1, 1)
fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)复制代码
make([]int, 3) 声明了个长度为3的slice,容量也是3。下面的append方法会添加一个新元素到slice里,长度和容量都会发生变化。
输出结果:
len: 3 cap: 3 array : [0 0 0]
len: 4 cap: 6 array : [0 0 0 1]复制代码
也能够经过重载方法指定slice的容量,下面:
slice2 := make([]int, 3, 7)
fmt.Println("len: ", len(slice2), "cap: ", cap(slice2), "array :", slice2)复制代码
输出长度为3,容量为7
使用字变量来建立Slice,有点像建立一个Array,可是不须要在[]指定长度,这也是Slice和Array的区别。Slice根据初始化的数据来计算度和容量
建立一个长度和容量为5的Slice
slice3 := []int{1, 2, 3, 4, 5}
fmt.Println("len: ", len(slice3), "cap: ", cap(slice3), "array :", slice3)复制代码
也能够经过索引来指定Slice的长度和容量,
下面建立了一个长度和容量为6的slice
slice4 := []int{5: 0}
fmt.Println("len: ", len(slice4), "cap: ", cap(slice4), "array :", slice4)复制代码
声明nil Slice,内部结构的指针为nil。能够用append给slice填加新的元素,内部的指针指向一个新的数组
var slice5 []int
fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)
slice5 = append(slice5, 4)
fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)复制代码
建立空Slice有两种方式
slice6 := []int{}
fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
slice6 = append(slice6, 2)
fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
slice7 := make([]int, 0)
fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)
slice7 = append(slice7, 7)
fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)复制代码
slice的切片
// 建立一个容量和长度均为6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 对slices1进行切片,长度为2容量为4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)复制代码
slice1的底层是一个容量为6的数组,slice2指底层指向slice1的底层数组,但起始位置为array的第一个元素也就是23.由于slices2从索引1开始的,因此没法访问底层数组索引1以前的元素,也没法访问容量以后的元素。能够看下图理解一下。
新建立的切片长度和容量的计算
对于一个新slice[x:y] 底层数组容量为z,
x: 新切片开始的元素的索引位置,上面的slice1[1:3]中的1就是起始索引
y:新切片但愿包含的元素个数,上面的slice1[1:3],但愿包含2个底层数组的元素 1+2=3
容量: z-x 上面的slice1[1:3] 底层数组的容量为6, 6-1=5因此新切片的容量为5
因为新建立的slice2和slice1底层是同一个数组,因此修改任何一个,两个slice共同的指向元素,会致使同时修改的问题
// 建立一个容量和长度均为6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 对slices1进行切片,长度为2容量为4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一个共同指向的元素
//两个slice的值都会修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)复制代码
如图所示
需注意的是,slice只能访问其长度范围内的元素,若是超出长度会报错。
除了修改共同指向元素外,若是新建立的切片长度小于容量,新增元素也会致使原来元素的变更。slice增长新元素使用内置的方法append。append方法会建立一个新切片。
// 建立一个容量和长度均为6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 对slices1进行切片,长度为2容量为4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一个共同指向的元素
//两个slice的值都会修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)
// 增长一个元素
slice2 = append(slice2, 55555)
fmt.Println(slice1)
fmt.Println(slice2)复制代码
若是切片的容量足够就把新元素合添加到切片的长度。若是底层的的数组容量不够时,会从新建立一个新的数组并把现有元素复制过去。
slice3 := []int{1, 2, 3}
fmt.Println("slice2 cap", cap(slice3))
slice3 = append(slice3, 5)
fmt.Println("slice2 cap", cap(slice3))复制代码
输出的结果为:
slice2 cap 3
slice2 cap 6复制代码
容量增加了一倍。
建立一个新的slice的时候能够限制他的容量
// 建立一个容量和长度均为6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 对slices1进行切片,长度为2容量为3
slice2 := slice1[1:3:4]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)复制代码
slice2长度为2容量为3,这也是经过上面的公式算出来的
长度:y-x 3-1=2
容量:z-x 4-1= 3
须要注意的是容量的长度不能大于底层数组的容量
绿颜色表示slice2中的元素,黄颜色表示容量中示使用的元素。可是须要注意的是,咱们修改或者增长slice2容量范围内的元素个数依然会修改slice1。
// 建立一个容量和长度均为6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 对slices1进行切片,长度为2容量为3
slice2 := slice1[1:3:4]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一个共同指向的元素
//两个slice的值都会修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)
// 增长一个元素
slice2 = append(slice2, 55555)
fmt.Println(slice1)
fmt.Println(slice2)复制代码