introduction
定義:切片為陣列的引用,因此切片為引用類型
- 切片本身並不會單獨存在,切片的底層就是陣列
- 切片的長度可以改變,可將切片視為可變陣列
- 切片的遍歷方式與陣列一樣
- 切片長度與陣列一樣可用
len
函數取得
cap
函數可以求出切片的容量,0 ≦ len(array) ≦ cap(slice)
聲明
- 與陣列不一樣的地方是
[]
中並無數字來表示長度,切片本身長度可以改變
1 2 3
| var slice_int []int
var slice_string []string
|
初始化
聲明切片變量後,請注意要初始化,否則操作時會造成越界,而拋出panic
make創建空切片
語法
1 2
| 1. var 變量 []類型 = make([]類型,長度,容量) 2. 變量 := make([]類型,長度,容量)
|
example 1
1
| var slice_int []int = make([]int,10,20)
|
example 2
1 2
| var slice_int []int slice_int = make([]int,10)
|
可簡寫成
1
| slice_int := make([]int,10)
|
聲明並初始化
1
| var slice_int []int = []int{1,2,3,4,5,6,7,8}
|
可讓Go自動做類型推導
1
| var slice_int = []int{1,2,3,4,5,6,7,8}
|
或是
1
| slice_int := []int{1,2,3,4,5,6,7,8}
|
- 實際上其為兩條語句的縮寫,因此應為聲明後才初始化
1 2
| var slice_int []int slice_int =[]int{1,2,3,4,5,6,7,8}
|
已知陣列切片後初始化
- 切片的語法為
[start:end]
,從start
開始到end
結束,但不包含end
- 如
start
為0開始,可省略不寫
- 如
end
為len(array)
,可省略不寫
- 如要包含整個陣列,兩個皆可省略不寫
1 2 3 4 5
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[:]
var array_int = [...]int{1,2,3,4,5,6,7,8,9,10} var slice_int = array_int[:9]
|
切片去掉最後一個元素
切片的end
值為陣列的長度減1
1 2
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[:len(array_int)-1]
|
切片去掉第一個元素
切片的end
值為1
1 2
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[1:]
|
容量(cap)與長度(len)
切片的容量與指向的數組的長度有關係
且0≦len(array)≦cap(slice)
example 1
1 2
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[:3]
|
result
1 2
| len(slice) = 3 cap(slice) = 8
|
因切片只取了陣列前面三位元素,後面還有五位元素未拿取,所以實際上切片的容量為8
- 只要切片的
start
值為0,容量(cap)一定是被指向的陣列之長度
example 2
1 2
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[3:7]
|
result
1 2
| len(slice) = 4 cap(slice) = 5
|
因切片start
從3開始,前面被切斷了,後面都是切片本身可以拿取的元素,所以容量為5
- 切片
start
數不為0的情況,容量為陣列的長度減掉start
值
example 3
1 2 3
| var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[3:5] var slice_twice_int = slice_int[2:]
|
result
1 2
| len(slice_twice) = 0 cap(slice_twice) = 3
|
對陣列的切片再進行切片,其容量仍與陣列相關
仍取決於指向的陣列後面有多少元素可以拿取
切片的內存佈局
切片的指針是指向陣列的(只是外部看不到,底層還是操作陣列)
因此將切片傳參進函數,在函數內操作切片中的元素(賦值、改值等),被指向陣列中的元素也會改變
更證明了切片本身為引用類型
example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import "fmt"
func modify_slice(s []int){ s[0] = 1000 s[1] = 2000 }
func main(){ var array_int = [8]int{1,2,3,4,5,6,7,8,} var slice_int = array_int[:]
modify_slice(slice_int)
fmt.Println("slice_int =",slice_int) fmt.Println("array_int =",array_int) }
|
result
1 2
| slice_int = [1000 2000 3 4 5 6 7 8] array_int = [1000 2000 3 4 5 6 7 8]
|
切片內存地址指向第一個元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package main
import "fmt"
func main(){ var slice1 = []int{2,4,6,8,10} var slice2 = slice1[1:3]
fmt.Printf("slice1_address =%p\n",slice1) fmt.Println("slice1[0] address =",&slice1[0])
fmt.Printf("slice1[1] address = %p\n",&slice1[1]) fmt.Printf("slice2 address = %p\n",slice2) }
|
[1] 切片本身已經為引用類型,不須再使用&
得到內存地址,因為它本身就是指針
- 若是使用
&
則會得到指向指針的指針類型,也就是存放這個指針類型(0xc04207e030)的地址 (重要)
result
1 2 3 4
| slice1_address =0xc04207e030 slice1[0] address = 0xc04207e030 slice1[1] address = 0xc04207e038 slice2 address = 0xc04207e038
|
細談 append
- append使用方法可參考day9-內置函數
- 使用
append
函數,將數個元素追加到一切片中,當超出切片初始化的容量(cap)時
原切片會自動擴容,幅度為原容量的一倍 (5 —> 10)1 2 3 4 5 6 7 8 9 10 11 12
| package main
import "fmt"
func main(){ var array = [5]int{2,4,6,8,10} var slice = array[1:3]
fmt.Println("before append cap(slice) =",cap(slice)) slice = append(slice,1,3,5) fmt.Println("after append cap(slice) =",cap(slice)) }
|
result1 2
| before append cap(slice) = 4 after append cap(slice) = 8
|
- 但是切片內部的指針便不是在指向原本的陣列,而是新的陣列
- 開闢新的內存空間創建新陣列,將原切片內的元素及拷貝到新陣列中,在追加新元素
- 這時再修改切片中的元素,跟原陣列無關
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import "fmt"
func main(){ var array = [5]int{2,4,6,8,10} var slice = array[1:3]
fmt.Println("Before append:") fmt.Println("array[1] address =",&array[1]) fmt.Printf("slice address =%p\n",slice)
slice = append(slice,1,3,5) fmt.Println("After append:") fmt.Println("array[1] address =",&array[1]) fmt.Printf("slice address =%p\n",slice) }
|
result
1 2 3 4 5 6
| Before append: array[1] address = 0xc04207e038 slice address =0xc04207e038 After append: array[1] address = 0xc04207e038 slice address =0xc042086080
|
- 如又使用
append
方法,再次追加元素超出擴容後的切片時,會再擴容(10 —> 20),且會再次指向新陣列
切片拷貝 (copy)
可將切片拷貝至其他切片中
語法
1
| copy(slice_target,slice_source)
|
將slice_source
拷貝至slice_target
中
- 拷貝的長度取決於
slice_target
容量的大小
- 即使
slice_source
的長度大於slice_target
的容量,拷貝過程不會擴容
target容量小於source
1 2 3 4 5 6 7
| var array = [5]int{2,4,6,8,10} var slice = array[1:3]
slice2 := make([]int,1) copy(slice2,slice)
fmt.Println(slice2)
|
result
target容量大於source
1 2 3 4 5 6 7
| var array = [5]int{2,4,6,8,10} var slice = array[1:3]
slice2 := make([]int,3) copy(slice2,slice)
fmt.Println(slice2)
|
result
字符串 (string)
string
的底層就是一個byte
類型的陣列([n]byte
),因此也可進行切片操作
string
為不可變類型,不能透過元素操作修改已定義的string
- 因此也沒有容量(cap)的概念
example
1 2 3 4 5 6 7 8 9 10
| package main
import "fmt"
func main(){ var str string = "Hello My World" fmt.Println(str[:5]) fmt.Println(str[6:8]) fmt.Println(str[9:]) }
|
result
string底層的內存布局
改變string中的字符
- 如真的欲改變字符串之值,可將不可變的
string
轉換為可變的[]byte
切片,再進行元素操作
- 再將操作完的
[]byte
切片轉換為string
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import "fmt"
func main(){ var str string = "Hello My World" str_conv := []byte(str)
str_conv[0] = 'h' str_conv[6] = 'm' str_conv[9] = 'w'
str = string(str_conv) fmt.Println(str) }
|
[1] 利用強制轉換[]byte()
將string
轉為[]byte
[2] 改變單個字符記得用單引號''
[3] 修改完成後強轉為string
類型
result
改變string中非ASCII字符
如果轉換非ASCII的字符,如中文字(三個byte)等,不能強轉為[]byte
操作元素
需轉換成[]rune
類型,在進行元素操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package main
import "fmt"
func main(){ var str string = "哈囉 世界" str_conv := []rune(str)
str_conv[0] = '你' str_conv[1] = '好'
str = string(str_conv) fmt.Println(str) }
|
[1] 利用強制轉換[]rune()
將string
轉為[]rune
[2] 改變單個字符記得用單引號''
[3] 修改完成後強轉為string
類型
result