切片
為什么需要切片
- 用于元素的個數不確定,所以無法通過數組的形式來進行統計。此時就需要切片
切片,也因此可以粗略地理解為動態數組 - 數組的長度不能用變量來確定,這時候切片slice也就派上用場了
切片地基本介紹
- 切片的英文是slice
- 切片是數組的一種引用,這也說明切片是引用類型,在傳遞時,遵守引用傳遞的機制
- 切片的使用和數組類似,遍歷、求長、訪問都是一樣的
- 切片的長度是可變的,因此說切片可以粗略理解為動態數組
- 切片定義的語法:
var 切片名 []類型
var a []int
切片的快速入門
var arr [5]int = [5]int{1, 2, 45, 6, 3}
a := arr[2:4]
fmt.Println(arr)
fmt.Println(a)
fmt.Printf(“a的長度%d\n”, len(a))
fmt.Printf(“a的容量%d\n”, cap(a))
fmt.Printf(“a指向的地址%v\n”, &a[0])
輸出結果為:
[1 2 45 6 3]
[45 6]
a的長度2
a的容量3
a指向的地址0xc000010240
可見,a := arr[2:4],這段代碼就是將arr數組的下標為2-3的元素。
其中cap()函數是用來求切片容量的,容量是指切片目前最多能存儲的元素個數,并且這個容量是可以動態更改的
切片在內存中的形式
- slice在內存中有三個值,一個是slice所指向的第一個元素的地址,一個是這個slice的長度,還有一個是這個slice的容量
- 根據這第一個值,我們也很容易判斷出slice是引用類型
- 其實,我們可以把slice看成一種結構體struct。其由這三個值組成。
type slice struct {
array *[n]T // 指向底層數組的指針
len int // 當前長度
cap int // 總容量
} - 另外,值得注意的一點是,由于slice是引用類型,所以當用slice[1]來修改值時,其所指的地址的值發送改變,也就是原數組的內容也會發送改變
代碼用例:
var arr [5]int = [5]int{1, 2, 45, 6, 3}
a := arr[2:4]
fmt.Println(arr)
fmt.Println(a)
a[0] = 100
fmt.Println(arr)
fmt.Println(a)
輸出結果為:
[1 2 45 6 3]
[45 6]
[1 2 100 6 3]
[100 6]
切片的使用
- 定義一個切片,讓切片去引用一個定義好的數組
- 用make來創建切片
基本語法:var 切片名 []類型 = make([]類型,切片長度,切片容量)
要求:容量必須大于等于長度
創建完的切片里的元素都是默認值
這樣定義出來的切片所指向的數組,是對外隱蔽的,只能通過slice來訪問這個數組
- 定義切片的時候,直接指定具體的數組
代碼示例:
func main() {
var arr []int = []int{1, 2, 41, 21}
fmt.Printf(“%v\n”, len(arr))
fmt.Printf(“%v”, arr)
} - 方式一和方式二的區別:
方式一:直接引用數組,是要求先定義好了一個數組,這個數組是可見的
方式二:用make創建切片的同時,也會創建數組,這個數組是由切片在底層進行維護的數組,是不可見的,只能通過slice來對它進行訪問修改等
切片的遍歷
- 傳統for循環
- for-range語句遍歷
func main() {
var n [5]int = [5]int{2, 12, 31, 12, 45}
slice1 := n[1:3]
for i := 0; i < len(slice1); i++ {
fmt.Printf("%v “, slice1[i])
}
fmt.Println()
for index, value := range slice1 {
fmt.Printf(”%v,%v ", index, value)
}
}
切片的使用細節和注意事項
- 切片初始化時,var slice = arr[start:end]
其中切片slice是從arr數組的start下標開始,到end下標結束,但是不將end下標的元素也囊括在內
- 在使用切片時,也只能按照0-len(slice)-1的范圍來,不能夠直接來越界初始化,需要先動態增長先
- slice :=arr[下標1:下標2]
- 如果不寫下標1,默認是0,從頭開始
- 如果不寫下標2,默認是end,把最后一位元素也算進去
- 所以要想表示切片獲取整個數組,有如下幾種簡寫
- slice :=arr[:]
- slice :=arr[0:]
- slice :=arr[:len(arr)]
- cap是一個內置函數,用于計算切片的容量,也就是最大可以存放多少元素
- 切片定義完后,本身還是空的,需要讓其引用數組,或者用make函數創建一個空間供切片使用
- 切片可以繼續切片
- append函數
- append的用法
- slice = append(slice, elem1, elem2, …)
給slice切片添加后面幾個元素,這里的…表示的是省略
- slice = append(slice, anotherSlice…)
在slice后面接收另一個切片,這里的…是不能省略的,表示將后面的切片各個元素拆開加入
不論append的哪種用法,都必須要有接收,不然相當于沒有任何作用,因為append函數并不會去改變原來的切片 - 擴不擴容
- 當長度在append后>容量時才會擴容,否則不會
2. 只有在擴容時,才會執行下面的(45678),因為如果不用擴容,就可以直接在原來的切片上更改,不需要額外創建新數組(擁有足夠大的容量)
- 當長度在append后>容量時才會擴容,否則不會
- append函數本質就是對數組的擴容
- append函數會創建一個新的數組newarr(是擴容后的大小)
- 將slice原來的元素拷貝到newarr
- 在newarr上添加新元素
- 返回新的切片(指向新數組),比如:slice重新引用到newarr
- newarr是底層維護的,程序員不可見
- append的用法
- 切片的拷貝
copy函數
語法copy(para1,para2)
para1,para2這兩個都需要是切片,并且是將para2的內容給到para1里面
此處不在乎二者的長度的大小關系,反正就是逐位去拷貝
拷貝并不會使這兩個切片的空間有什么關聯,依然是相互獨立的
- 切片是引用類型,所以傳遞時遵循引用傳遞,向函數傳遞切片時,函數內部對切片的操作,也會對外部操作有影響
代碼示例:
func test(a []int) {
a[0] = 100
}
func main() {
var a []int = make([]int, 4, 8)
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
fmt.Println(a)
test(a)
fmt.Println(a)
}
輸出結果:
[1 2 3 4]
[100 2 3 4]
string和slice的關系
- string的底層是一個byte數組,因此string也可以用切片來處理
- string的核心結構:
type stringStruct struct {
str unsafe.Pointer // 指向底層字節數組的指針
len int // 字符串的字節長度(不是字符數)
}
這和切片的結構是很像的,只是沒有容量,因為數組的容量大小就是長度大小,因為它長度不可變 - string本身是不可變的,不能通過str[0]="1"這種類似形式去修改(和java一樣)
- 如果要修改string的值,需要先將string轉換成[]byte或者[]rune,修改完后,再轉成string
有中文的情況轉成[]rune,因為[]rune是按照字符,一個中文占三四個字節