golang切片及常见问题
切片
切片并不是数组或者数组指针,切片只是对数组中连续片断的引用,这个片断可以是整个数组,也可以是由起始索引和终止索引标识的数组,所以切片是一个引用类型。
// 切片的数据结构 struct Slice{ byte* array; uintgo len; uintgo cap; }
-
切片是引用类型,但是自身是结构体,调用函数时是值传递。
-
len 属性获取长度,cap 属性获取容量
初始化
一个切片在未初始化之前默认为 nil,长度为 0
func main() {
#初始化3种方法 s1 := []int{} var s2 []int s3 := make([]int,0)
#常见报错 // s1[0] = 1 以下三种赋值都会报错 // s2[0] = 1 panic: runtime error: index out of range [0] with length 0 // s3[0] = 1 s1 = append(s1, 1) // Slice 正确添加值的方式 s2 = append(s2, 1) s3 = append(s3, 1) fmt.Println(s1) fmt.Println(s2) fmt.Println(s3) }
通过数组创建切片:
func main() { arrStr := [10]string{"a","b","c","d","e","f","h","i","j","k"} #数组 slice1 := arrStr[:] // 包含数组的全部元素 slice2 := arrStr[3:] // 包含从第三个元素起一直到最后一个元素 slice3 := arrStr[:8] // 包含第8个元素之前的所有元素
fmt.Println(arrStr)
//arrStr: [a b c d e f h i j k]
fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3) // 输出: #切片的格式 // [a b c d e f h i j k] // [d e f h i j k] // [a b c d e f h i] }
append
切片是一个引用类型,底层是一个数组,所以切片很容易就可以完成动态扩容。
func main() { slice := make([]int,0) for i := 0; i < 10; i++ { slice = append(slice, i) fmt.Printf("长度:%d,容量:%d\n", len(slice), cap(slice)) } // 输出: // 长度:1,容量:1 // 长度:2,容量:2 // 长度:3,容量:4 // 长度:4,容量:4 // 长度:5,容量:8 // 长度:6,容量:8 // 长度:7,容量:8 // 长度:8,容量:8 // 长度:9,容量:16 // 长度:10,容量:16 }
切片后添加值使用 append 方法,直接在切片尾部添加值,并返回一个新的切片,之前的切片如果没有使用,就会被GC回收.
根据上面的代码,可以得出:切片在每次长度=容量之后,切片的容量就会扩大两倍
func main() { slice := make([]int,0) for i := 0; i < 1030; i++ { slice = append(slice, i) if i > 1022{ fmt.Printf("长度:%d,容量:%d\n", len(slice), cap(slice)) } } // 输出: // 长度:1024,容量:1024 // 长度:1025,容量:1280 // 长度:1026,容量:1280 // 长度:1027,容量:1280 }
根据这一次的代码,可以得出:切片的容量超过 1024 (1M)之后,每一次扩容都会增加前一次容量的 1/4
- 切片的容量在 1024 之下时,每一次扩容容量翻倍
- 切片的容量在 1024 之上时,每一次扩容容量增加前一次容量的 1/4
切片的遍历
package main import ( "fmt" ) func main() { var arr [5]int = [...]int{10, 20, 30, 40, 50} slice := arr[1:4] fmt.Printf("slice: %v\n", slice) //[20 30 40] for i := 0; i < len(slice); i++ { fmt.Printf("slice[%v]=%v\n", i, slice[i]) } //使用for-range遍历切片 for index, val := range slice { fmt.Printf("slice[%v]=%v\n", index, val) } //用append内置函数,可以对切片进行动态追加 var slice3 []int = []int{100, 200, 300} //通过append直接给slice追加具体的元素 slice4 := append(slice3, 400, 500, 600) fmt.Printf("slice4: %v\n", slice4) slice3 = append(slice3, 400, 500, 600) fmt.Printf("slice3: %v\n", slice3) //通过append讲切片slce3追加给slice3 slice3 = append(slice3, slice3...) //追加数组时固定写法slice... fmt.Println("slice3=", slice3) }
常见问题
1、切片是连续内存并且可以动态扩展,由此引发的问题?
a := []int b := []int{1,2,3} c := a a = append(b,1)
最佳实践:在切片做append的时候,append的哪个就赋值给哪个;易错点:在append b时,若b所在的那一块内存空间达到上限,会重新copy生成一格新的内存地址然后append 1,在这过程之后 a赋值给c之前和之后的地址已经不一样了,那么对应的值也就不一样。
2、修改切片的值? 【go语言都是值传递】
package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5, 6, 7} for _, value := range slice { value *= 2 } fmt.Println("myslice\n", slice) for index, _ := range slice { slice[index] *= 2 } fmt.Println("myslice", slice) }
go语言都是值传递,改变切片的值需要通过索引去修改;使用value*2只是临时生成一个value变量,去修改他的值,不会修改slice里的值