数组大家都晓得是具备 固定长度及类型的序列汇合 , 然而 golang 中又引入了 切片,语法上看起来还和数组差不多,为什么会引入这些呢?切片和数组到底有什么区别呢?接下来咱们来一个一个的看
数组 array
定义数组
var arr [5]int = [4]int{1,2,3,4,5}
上述语句示意的意思是,咱们来定义一个变量 arr 为 5 个长度的 int 的数组类型,也就是[5]int, 同时给赋值上了初始值 1、2、3、4、5,内存散布如图
留神
如果定义数组的办法是
arr := new([4]int)
那么 arr 的数据类型为 *[4]int,而不是[4]int
不定长数组
当然数组的长度 4 如果是不固定的,能够用 … 的形式代替
q := [...]int{1, 2, 3}
数组的循环
数组的循环在 golang 中有一个特有的语法,就是 for range
var arr [4]int = [4]int{1, 2, 3, 4}
for i, v := range arr {fmt.Printf("数组中的第 %v 项, 值是 %v\n", i, v)
}
// 输入后果
数组中的第 0 项, 值是 1
数组中的第 1 项, 值是 2
数组中的第 2 项, 值是 3
数组中的第 3 项, 值是 4
数组的罕用办法
罕用办法是len() 办法和 cap() 办法
- len()办法的作用是获取数组或者切片的 长度
- cap()办法的作用是获取数组或者切片的 容量
然而 在数组中,这两个值永远雷同, 所以在这里咱们不多做思考,在前面切片中再具体论述。
切片 slice
为什么会有切片?
切片之所以会诞生,是因为 golang 中数组存在很大的两个问题
- 固定的长度,这意味着初始化 array 后,不能再 push 超过 len(array) 长度的元素
-
array 作为参数在函数之间传递时是值传递,相当于把数据 copy 了一份,具备很大的性能节约
切片数据类型的底层构造
type slice struct { array unsafe.Pointer // 指向一个数组的指针 len int // 以后 slice 的长度 cap int // 以后 slice 的容量 }
比方咱们定义了一个切片
s := make([]int, 3, 5) s[0] = 1 s[1] = 2 s[2] = 3
那么以上变量在内存中的数据结构如下图所示
所以由下面的剖析能够看进去,切片是依赖于数组的,而且是一个指向数组的指针, 既然切片是指针类型,那么在作为参数传递的时候,必定是援用类型,不须要从新 copy 一份而造成空间节约。
slice 的截取
咱们下面说过切片是依赖于数组的,所以切片的截取是基于数组进行截取的,截取这块咱们间接看例子就行,看例子记住一个准则即可 左蕴含,右不蕴含
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s4 := a1[2:4] // 输入后果[3 4]
s5 := a1[:4] // 输入后果[1 2 3 4]
s6 := a1[2:] // 输入后果[3 4 5 6 7 8 9]
s7 := a1[:] // 输入后果[1 2 3 4 5 6 7 8 9]
以上例子都合乎下面提到的 左蕴含,右不蕴含准则
- s4 从下标 2 开始截取,截取到下标 4
- s5 省略了第一个参数,示意从下标 0 开始截取
- s6 省略了第二个参数,示意截取到最初一个元素
-
s7 省略了两个参数,只填写了两头的冒号:,示意取全副元素
切片的长度 len()和容量 cap()
长度很好了解,简略了解就是 元素的个数 ,容量绝对难了解一些 在切片援用的底层数组中从切片的第一个元素到数组最初一个元素的长度就是切片的容量
咱们还是来间接看例子
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s5 := a1[:4] //[1 2 3 4]
s6 := a1[2:] //[3 4 5 6 7 8 9]
s7 := a1[:] //[1 2 3 4 5 6 7 8 9]
fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) //4 9
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) //7 7
fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7)) //9 9
- a1 是数组长度为 9,容量也为 9,值是从 1~9
- s5/s6/s7 都是切割数组 a1 失去的切片。
- s5 的长度为 4,因为只有 1 2 3 4 这 4 个元素,容量为 9,因为 s5 切片是从数组起始地位开始切割的:第一个元素是 1,而 s5 底层数组 a1 最初一个元素是 9,1~9 共 9 个元素,所以 s5 的容量为 9。
- s6 的长度为 7,因为 s6 的元素是 39 这 7 个元素;容量也为 7,因为 s5 的底层数组最初一个元素是 9,39 共 7 个元素,所以 s6 的容量为 7。
-
s7 更好了解了,长度和容量都是 9,大家本人了解一下。
切片的罕用办法
make
make 办法次要是用于切片的生成,比较简单, 比方上面的例子就是咱们来定义一个长度为 5,容量为 10 的切片。
s1 := make([]int,5,10) fmt.Printf("s1:%v len(s1):%d cap(s1):%d\n", s1, len(s1), cap(s1)) // 输入后果 //s1:[0 0 0 0 0] len(s1):5 cap(s1):10
append
append 次要是用于切片的追加。咱们还是间接看例子
var s = []int{1, 2, 3, 4} fmt.Println(s) fmt.Printf("len:%d, cap:%d", len(s), cap(s)) // 输入后果 [1 2 3 4] len:4, cap:4
咱们能够看到定义了一个切片,初始化了 4 个元素,切片此时的长度和容量都为 4
var s = []int{1, 2, 3, 4} s = append(s, 5) // 给切片 s 追加一个元素 5 fmt.Println(s) fmt.Printf("len:%d, cap:%d\n", len(s), cap(s)) // 输入后果 [1 2 3 4 5] len:5, cap:8
剖析:长度由 4 变成 5,咱们很好了解;容量为什么会从 4 变成 8 呢?这个是因为 go 语言对切片的主动扩容机制,append 追加,如果 cap 不够的时候,go 底层会把底层数组替换,是 go 语言的一套扩容策略。 简略说这个扩容机制就是 如果不够,就在以前的根底上翻倍,如果超过 1M,则 +1M,跟 redis 的 bitmap 类型的扩容机制是一样的
slice 扩容的 ” 坑 ”
func main() {var s = []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // 打印 [1 2 3]
}
func modifySlice(s []int) {s = append(s, 4)
s[0] = 4
}
这个坑在面试中常常会遇到,当 slice 作为函数参数时,如果在函数外部产生了扩容,这时再批改 slice 中的值是不起作用的,因为批改产生在新的 array 内存中,对老的 array 内存不起作用。
如何追加多个元素
s1 := []int{1, 2, 3, 4}
s2 := []int{5, 6}
s3 := append(s1, s2...) // ... 示意拆开,将切片的值作为追加的元素
fmt.Println(s3)
// 输入后果
//[1 2 3 4 5 6]
copy
// 定义切片 s1
s1 := []int{1, 2, 3}
// 第一种形式:间接申明变量 用 = 赋值
//s2 切片和 s1 援用同一个内存地址
var s2 = s1
// 第二种形式:copy
var s3 = make([]int, 3)
copy(s3, s1) // 应用 copy 函数将 参数 2 的元素复制到参数 1
s1[0] = 11
fmt.Printf("s1:%v s2:%v s3:%v",s1, s2, s3) //s1 和 s2 是[11 2 3] s3 是[1 2 3]
咱们发现 s1 和 s2 是[11 2 3] s3 是[1 2 3],阐明 copy 办法是复制了一份,开拓了新的内存空间,不再援用 s1 的内存地址,这就是两者的区别。
如果你感觉这篇文章还不错,记得点个赞点个关注。同时也能够关注 GZH” 程序员小饭 ”,您的反对是我创作的最大能源
本文由 mdnice 多平台公布