introduction
定義:切片為陣列的引用,因此切片為引用類型
- 切片本身並不會單獨存在,切片的底層就是陣列
- 切片的長度可以改變,可將切片視為可變陣列
- 切片的遍歷方式與陣列一樣
- 切片長度與陣列一樣可用len函數取得
- cap函數可以求出切片的容量,0 ≦ len(array) ≦ cap(slice)- 
 
聲明
- 與陣列不一樣的地方是[]中並無數字來表示長度,切片本身長度可以改變
| 12
 3
 
 | var slice_int []int
 var slice_string []string
 
 | 
初始化
聲明切片變量後,請注意要初始化,否則操作時會造成越界,而拋出panic
make創建空切片
語法
| 12
 
 | 1. var 變量 []類型 = make([]類型,長度,容量)2. 變量 := make([]類型,長度,容量)
 
 | 
example 1
| 1
 | var slice_int []int = make([]int,10,20)
 | 
example 2
| 12
 
 | var slice_int []intslice_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}
 | 
- 實際上其為兩條語句的縮寫,因此應為聲明後才初始化| 12
 
 | var slice_int []intslice_int =[]int{1,2,3,4,5,6,7,8}
 
 |  
 
已知陣列切片後初始化
- 切片的語法為[start:end],從start開始到end結束,但不包含end
- 如start為0開始,可省略不寫
- 如end為len(array),可省略不寫
- 如要包含整個陣列,兩個皆可省略不寫
| 12
 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
| 12
 
 | var array_int = [8]int{1,2,3,4,5,6,7,8,}var slice_int = array_int[:len(array_int)-1]
 
 | 
切片去掉第一個元素
切片的end值為1
| 12
 
 | 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
| 12
 
 | var array_int = [8]int{1,2,3,4,5,6,7,8,}var slice_int = array_int[:3]
 
 | 
result
| 12
 
 | len(slice) = 3cap(slice) = 8
 
 | 
因切片只取了陣列前面三位元素,後面還有五位元素未拿取,所以實際上切片的容量為8
- 只要切片的start值為0,容量(cap)一定是被指向的陣列之長度
example 2
| 12
 
 | var array_int = [8]int{1,2,3,4,5,6,7,8,}var slice_int = array_int[3:7]
 
 | 
result
| 12
 
 | len(slice) = 4cap(slice) = 5
 
 | 
因切片start從3開始,前面被切斷了,後面都是切片本身可以拿取的元素,所以容量為5
- 切片start數不為0的情況,容量為陣列的長度減掉start值
example 3
| 12
 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
| 12
 
 | len(slice_twice) = 0cap(slice_twice) = 3
 
 | 
對陣列的切片再進行切片,其容量仍與陣列相關
仍取決於指向的陣列後面有多少元素可以拿取
切片的內存佈局

切片的指針是指向陣列的(只是外部看不到,底層還是操作陣列)
因此將切片傳參進函數,在函數內操作切片中的元素(賦值、改值等),被指向陣列中的元素也會改變
更證明了切片本身為引用類型
example
| 12
 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
| 12
 
 | slice_int = [1000 2000 3 4 5 6 7 8]array_int = [1000 2000 3 4 5 6 7 8]
 
 | 
切片內存地址指向第一個元素
| 12
 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
| 12
 3
 4
 
 | slice1_address =0xc04207e030slice1[0] address = 0xc04207e030
 slice1[1] address = 0xc04207e038
 slice2 address = 0xc04207e038
 
 | 
細談 append
- append使用方法可參考day9-內置函數
- 使用append函數,將數個元素追加到一切片中,當超出切片初始化的容量(cap)時
 原切片會自動擴容,幅度為原容量的一倍 (5 —> 10)| 12
 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))
 }
 
 |  
 
| 12
 
 | before append cap(slice) = 4after append cap(slice) = 8
 
 |  
 
- 但是切片內部的指針便不是在指向原本的陣列,而是新的陣列
- 開闢新的內存空間創建新陣列,將原切片內的元素及拷貝到新陣列中,在追加新元素
- 這時再修改切片中的元素,跟原陣列無關| 12
 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
| 12
 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
| 12
 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
| 12
 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
| 12
 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
| 12
 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類型,在進行元素操作
| 12
 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