Hike News
Hike News

Go初識-day11-切片

introduction

定義:切片為陣列的引用,因此切片為引用類型

  • 切片本身並不會單獨存在,切片的底層就是陣列
  • 切片的長度可以改變,可將切片視為可變陣列
  • 切片的遍歷方式與陣列一樣
  • 切片長度與陣列一樣可用len函數取得
  • cap函數可以求出切片的容量,0 ≦ len(array) ≦ cap(slice)
    • array是指被引用的陣列

聲明

1
var 變量名 []類型
  • 與陣列不一樣的地方是[]中並無數字來表示長度,切片本身長度可以改變
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}

已知陣列切片後初始化

  1. 切片的語法為[start:end],從start開始到end結束,但不包含end
  2. start為0開始,可省略不寫
  3. endlen(array),可省略不寫
  4. 如要包含整個陣列,兩個皆可省略不寫
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

對陣列的切片再進行切片,其容量仍與陣列相關
仍取決於指向的陣列後面有多少元素可以拿取


切片的內存佈局

slice

切片的指針是指向陣列的(只是外部看不到,底層還是操作陣列)
因此將切片傳參進函數,在函數內操作切片中的元素(賦值、改值等),被指向陣列中的元素也會改變
更證明了切片本身為引用類型

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) //[1]
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))
    }

result

1
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

1
[4]

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

1
[4 6 0]

字符串 (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

1
2
3
Hello
My
World

string底層的內存布局

string

改變string中的字符

  1. 如真的欲改變字符串之值,可將不可變的string轉換為可變的[]byte切片,再進行元素操作
  2. 再將操作完的[]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) //[1]

str_conv[0] = 'h' //[2]
str_conv[6] = 'm'
str_conv[9] = 'w'

str = string(str_conv) //[3]
fmt.Println(str)
}

[1] 利用強制轉換[]byte()string轉為[]byte
[2] 改變單個字符記得用單引號''
[3] 修改完成後強轉為string類型

result

1
hello my world


改變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) //[1]

str_conv[0] = '你' //[2]
str_conv[1] = '好'

str = string(str_conv) //[3]
fmt.Println(str)
}

[1] 利用強制轉換[]rune()string轉為[]rune
[2] 改變單個字符記得用單引號''
[3] 修改完成後強轉為string類型

result

1
你好 世界