package main
import "fmt"
func main() {
var a [5]int // 声明长度为5的整形数组
var b [3]int = [3]int{1,2,3} //声明长度为3的整形数组,并初始化
c := [3]int{1,2,3} // 声明一个数组,并赋值
d := [...]int{1,2,3} // 声明一个数组, [...]代表数组的长度,由数组初始化时存放的元素个数决定
e := [...]int{3:11,12,13} // 声明并初始化一个数组,前三位是默认值,后面三位的值是11,12,13
fmt.Printf("a = %+d \n", a)
fmt.Printf("b = %+d \n", b)
fmt.Printf("c = %+d \n", c)
fmt.Printf("d = %+d \n", d)
fmt.Printf("e = %+d \n", e)
}
输出结果:
a = [+0 +0 +0 +0 +0]
b = [+1 +2 +3]
c = [+1 +2 +3]
d = [+1 +2 +3]
e = [+0 +0 +0 +11 +12 +13]
数组是定长的,长度也是数组属性的一部分,声明之后就不再改变。
数组是否可比较,取决于数组中的元素是否可以比较。如果数组中的元素可以比较我们可以直接通过==操作符来比较两个数组是否相等。
我们可以通过len
方法,获取一个数组的长度。
数组是值类型
,作为函数的参数传递时会拷贝整个数组。所以我们往往不直接使用大数组
作为函数参数,而是使用*
取址操作符,把数组的地址作为参数传递,以此来减少数组拷贝带来的性能开销。
Go语言函数的传参方式不存在传值
还是传引用
的问题,在Go语言看传递的是值还是引用,就看参数自身是数值类型还是引用类型就可以了。
画外音:Go语言中的引用类型有:切片、字典、通道、函数等。
slice是Go语言中特有的数据结构。一个切片底层必然依赖某一个数组,是这个数组连续片段的引用。但是切片比数组更加灵活,可以随着切片元素增加进行自动扩容
。
上文中所有初始化数组的方式,把[]中间长度去掉,都可以用来声明切片。除了上文中的方式,切片还有以下声明方式:
package main
import "fmt"
func main() {
arr := [5]int{1,2,3,4,5}
// 通过数组初始化一个切片
a := arr[1:3] //指定一个数组下标中一个左闭又开的区间作为切片的初始值
b := arr[:3] //省略:左侧的数值,左侧的默认是为0
c := arr[1:] //省略:右侧的数值,右侧的默认值是数组的长度
d := arr[:] // :左右两侧的数值都省略,默认复制整个数组的值给切片
// 使用make初始化切片
e := make([]int,5,10) //初始化一个长度是5,容量是10的切片
f := make([]int,5) //初始化一个长度是5的切片,当不指定容量时,容量默认等于切片的长度
fmt.Printf("a = %+d \n", a)
fmt.Printf("b = %+d \n", b)
fmt.Printf("c = %+d \n", c)
fmt.Printf("d = %+d \n", d)
fmt.Printf("e = %+d \n", e)
fmt.Printf("f = %+d \n", f)
}
切片有长度和容量两个属性,长度代表切片当前存放元素的数量,容量代表切片当前最多可存放元素的数量。往切片内插入的元素超过其容量时,切片会发生扩容。
切片相关的方法:len()和cap()分别用来获取切片的长度和容量
package main
import "fmt"
func main() {
// 使用make初始化切片
a := make([]int,5,10)
fmt.Printf("len = %+d \n", len(a))
fmt.Printf("cap = %+d \n", cap(a))
}
输出结果:
len = +5
cap = +10
append():往切片末尾追加元素 copy();把一个切片的值拷贝到另一个切片
package main
import (
"fmt"
)
func main() {
s1 := make([]int,5,10)
s1 = append(s1, 9,10,11) // 往s1中追加 9,10,11 三个元素
s2 := make([]int,len(s1),10) // 在指定s2的长度时候,默认和s1相同,如果不相同的话会是什么样的呢?
copy(s2, s1) // 把s1中的值拷贝一份到s2
fmt.Printf("s1 = %+d \n", s1)
fmt.Printf("s2 = %+d \n", s2)
s1 = append(s1, 12,13,14) //继续往s1中添加元素,并超过切片原始的容量10
fmt.Printf("len(s1) = %+d \n", len(s1))
fmt.Printf("cap(s1) = %+d \n", cap(s1))
}
输出结果:
s1 = [+0 +0 +0 +0 +0 +9 +10 +11]
s2 = [+0 +0 +0 +0 +0 +9 +10 +11]
len(s1) = +11
cap(s1) = +20
copy(s2,s1)
把s1中元素的值复制给s2,如果s2的长度小于s1,则只拷贝s2长度内的元素。连续往s1添加元素,添加元素数量超过s1的容量时,切片发生了扩容。切片扩容的动作是不改变原有的切片,而是生成一个容量更大的切片,把现有的元素和新的元素一起拷贝到新切片中。
切片扩容的策略: 1.如果新申请容量大于当前容量的两倍数,则使用新申请的容量。 2.如果新申请容量小于等于当前容量两倍,当前容量如果小于1024,则新容量变为当前容量的两倍;如果当前容量大于1024,则新增当前容量的1.25倍,和新申请的容量对比,直到大于新的容量。
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
我们验证下切片扩容过程中,申请了新的切片,而且老的切片不变。
package main
import (
"fmt"
"unsafe"
)
func main() {
s1 := []int{1,2,3,4,5}
ptr := unsafe.Pointer(&s1[0]) // unsafe.Pointer方法 用来获取变量所在的内存地址
fmt.Printf("s1第一元素一开始的地址 = %+d \n", ptr)
s1 = append(s1, 6,7)
s1[0] = 100
fmt.Printf("s1扩容后第一个元素的地址 = %+d \n", unsafe.Pointer(&s1[0]))
s := (*int)(ptr)
fmt.Printf("s1第一个元素的数值=%+d",*s)
}
输出结果:
s1第一元素一开始的地址 = +824634318848
s1扩容后第一个元素的地址 = +824634351616
s1第一个元素的数值=+1
s1扩容前后切片第一个元素的内存地址不相同,可以判定,两者不是同一个对象;扩容之后s1第一个元素的值没有变化可以判定,切片扩容没有修改原始切片的值。如果此时把s1切片,扩容前所有元素都打印出来,值也是没有变化的。
切片扩容有一个容易踩坑的点,请看代码。
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[0:2]
s2 := append(s1, 100)
fmt.Printf("第一次打印 s1 = %v, Pointer = %p, len = %d, cap = %d\n", s1, &s1, len(s1), cap(s1))
fmt.Printf("第一次打印 s2 = %v, Pointer = %p, len = %d, cap = %d\n", s2, &s2, len(s2), cap(s2))
s2[1] += 50
fmt.Printf("第二次打印 s1 = %v, Pointer = %p, len = %d, cap = %d\n", s1, &s1, len(s1), cap(s1))
fmt.Printf("第二次打印 s2 = %v, Pointer = %p, len = %d, cap = %d\n", s2, &s2, len(s2), cap(s2))
fmt.Printf("arr = %v\n", arr)
}
输出结果:
第一次打印 s1 = [1 2], Pointer = 0xc000114000, len = 2, cap = 5
第一次打印 s2 = [1 2 100], Pointer = 0xc000114018, len = 3, cap = 5
第二次打印 s1 = [1 52], Pointer = 0xc000114000, len = 2, cap = 5
第二次打印 s2 = [1 52 100], Pointer = 0xc000114018, len = 3, cap = 5
arr = [1 52 100 4 5]
通过结果我们可以看到,修改切片s2的元素值,也改变了数组arr的值,因为s1指向arr数组,所有s1的值也发生了变化。此时是因为数组的长度够长,扩容之后并不会新申请数组,切片还是指向老的数组。
Go切片的源码在:runtime包的slice.go文件中。