Go数组是值类型,数组定义的时候就须要指定大小,不一样大小的数组是不一样的类型,数组大小固定以后不可改变。数组的赋值和传参都会复制一份。数组
func main() {
arrayA := [2]int{100, 200}
var arrayB [2]int
arrayB = arrayA
fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)
testArray(arrayA)
}
func testArray(x [2]int) {
fmt.Printf("func Array : %p , %v\n", &x, x)
}
复制代码
结果:markdown
arrayA : 0xc4200bebf0 , [100 200]
arrayB : 0xc4200bec00 , [100 200]
func Array : 0xc4200bec30 , [100 200]
复制代码
能够看到,三个内存地址都不一样,这也就验证了 Go 中数组赋值和函数传参都是值复制的。尤为是传参的时候把数组复制一遍,当数组很是大的时候会很是消耗内存。能够考虑使用指针传递。数据结构
指针传递有个很差的地方,当函数内部改变了数组的内容,则原数组的内容也改变了。app
所以通常参数传递的时候使用slice函数
切片自己并非动态数组或者数组指针。它内部实现的数据结构经过指针引用底层数组,设定相关属性将数据读写操做限定在指定的区域内。切片自己是一个只读对象,其工做机制相似数组指针的一种封装。
oop
切片(slice)是对数组一个连续片断的引用,因此切片是一个引用类型。这个片断能够是整个数组,或者是由起始和终止索引标识的一些项的子集。须要注意的是,终止索引标识的项不包括在切片内。切片提供了一个与指向数组的动态窗口。
ui
给定项的切片索引可能比相关数组的相同元素的索引小。和数组不一样的是,切片的长度能够在运行时修改,最小为 0 最大为相关数组的长度:切片是一个长度可变的数组。
spa
切片数据结构定义3d
type slice struct {
array unsafe.Pointer
len int
cap int
}
复制代码
切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 表明当前切片的长度,cap 是当前切片的容量。cap 老是大于等于 len 的。
若是想从 slice 中获得一块内存地址,能够这样作:指针
s := make([]byte, 200)
ptr := unsafe.Pointer(&s[0])
复制代码
使用make函数建立slice
// 建立一个初始大小是3,容量是10的切片
s1 := make([]int64,3,10)
复制代码
底层方法实现:
func makeslice(et *_type, len, cap int) slice {
// 根据切片的数据类型,获取切片的最大容量
maxElements := maxSliceCap(et.size)
// 比较切片的长度,长度值域应该在[0,maxElements]之间
if len < 0 || uintptr(len) > maxElements {
panic(errorString("makeslice: len out of range"))
}
// 比较切片的容量,容量值域应该在[len,maxElements]之间
if cap < len || uintptr(cap) > maxElements {
panic(errorString("makeslice: cap out of range"))
}
// 根据切片的容量申请内存
p := mallocgc(et.size*uintptr(cap), et, true)
// 返回申请好内存的切片的首地址
return slice{p, len, cap}
}
复制代码
利用数组建立切片
arr := [10]int64{1,2,3,4,5,6,7,8,9,10}
s1 := arr[2:4:6] // 以arr[2:4]建立一个切片,且容量到达arr[6]的位置,即cap=6-2=4,若是不写容量则默认为数组最后一个元素
复制代码
nil切片的指针指向的是nil
空切片指向的是一个空数组
空切片和 nil 切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,可是它没有分配任何内存空间,即底层元素包含0个元素。
最后须要说明的一点是。不论是使用 nil 切片仍是空切片,对其调用内置函数 append,len 和 cap 的效果都是同样的
func main() {
slice := []int{10, 20, 30, 40}
newSlice := append(slice, 50)
fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
newSlice[1] += 10
fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
}
复制代码
输出结果:
Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
复制代码
Go 中切片扩容的策略是这样的:
若是切片的容量小于 1024 个元素,因而扩容的时候就翻倍增长容量。上面那个例子也验证了这一状况,总容量从原来的4个翻倍到如今的8个。
一旦元素个数超过 1024 个元素,那么增加因子就变成 1.25 ,即每次增长原来容量的四分之一。
不必定。当发生了扩容就确定是新数组,没有发生扩容则是旧地址
无论切片是经过make建立仍是字面量建立,底层都是同样的,指向的是一个数组。当使用字面量建立时,切片底层使用的数组就是建立时候的数组。修改切片中的元素或者往切片中添加元素,若是没有扩容,则会影响原数组的内容,切片底层和原数组是同一个数组;当切片扩容了以后,则修改切片的元素或者往切片中添加元素,不会修改数组内容,由于切片扩容以后,底层数组再也不是原数组,而是一个新数组。
因此尽可能避免切片底层数组与原始数组相同,尽可能使用make建立切片
func main() {
// slice := []int{10, 20, 30, 40}
slice := [4]int{10, 20, 30, 40}
for index, value := range slice {
fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index])
}
}
复制代码
结果:
value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378
复制代码
从上面结果咱们能够看到,若是用 range 的方式去遍历一个数组或者切片,拿到的 Value 实际上是切片里面的值拷贝。因此每次打印 Value 的地址都不变。
因为 Value 是值拷贝的,并不是引用传递,因此直接改 Value 是达不到更改原切片值的目的的,须要经过 &slice[index] 获取真实的地址
尤为是在for循环中使用协程,必定不能直接把index,value传入协程,而应该经过参数传进去
错误示例:
func main() {
s := []int{10,20,30}
for index, value := range s {
go func() {
time.Sleep(time.Second)
fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value))
}()
}
time.Sleep(time.Second*2)
}
结果:
index:2,value:30
index:2,value:30
index:2,value:30
复制代码
正确示例:
func main() {
s := []int{10,20,30}
for index, value := range s {
go func(i,v int) {
time.Sleep(time.Second)
fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v))
}(index,value)
}
time.Sleep(time.Second*2)
}
结果:
index:0,value:10
index:2,value:30
index:1,value:20
复制代码