您現在的位置是:首頁 > 攝影首頁攝影

golang2021資料格式(18)切片

由 清華學神尹成程式設計帝國 發表于 攝影2022-12-19
簡介經常使用的切片建立方法:自動推導型別建立slices1 := [] int {1, 2, 3, 4}建立 有 4 個元素的切片,分別為:1234藉助make建立 slice,格式:make(切片型別,長度,容量)s2 := make([

切片儲存的格式有哪幾種

陣列的長度在定義之後無法再次修改;陣列是值型別,每次傳遞都將產生一份副本。顯然這種資料結構無法完全滿足開發者的真實需求。Go語言提供了陣列切片(slice)來彌補陣列的不足。

Slice(切片)代表變長的序列,序列中每個元素都有相同的型別。一個slice型別一般寫作[]T,其中T代表slice中元素的型別;slice的語法和陣列很像,只是沒有固定長度而已。

陣列和slice之間有著緊密的聯絡。一個slice是一個輕量級的資料結構,提供了訪問陣列子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組物件。一個slice由三個部分構成:指標、長度和容量。指標指向第一個slice元素對應的底層陣列元素的地址,要注意的是slice的第一個元素並不一定就是陣列的第一個元素。

切片並不是陣列或陣列指標,它透過內部指標和相關屬性引陣列段,以實現變案。

slice並不是真正意義上的動態陣列,而是一個引用型別。slice總是指向一個底層array,slice的宣告也可以像array一樣,只是不需要長度。

golang2021資料格式(18)切片

在講解切片(slice)之前,大家思考一下陣列有什麼問題?

第一:陣列定義完,長度是固定的。

例如:

golang2021資料格式(18)切片

定義的num陣列長度是5,表示只能儲存5個整型數字,現在向陣列num中追加一個數字,這時會出錯。

第二:使用陣列作為函式引數進行傳遞時,如果實參為5個元素的整型陣列,那麼形參也必須5個元素的整型陣列,否則出錯。

針對以上兩個問題,可以使用切片來進行解決。

切片:切片與陣列相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大,所以可以將切片理解成“動態陣列”,但是,它不是陣列。

slice和陣列的區別:宣告陣列時,[ ]內寫明瞭陣列的長度或使用。。。自動計算長度,而宣告slice時,[ ]內沒有任何字元。經常使用的切片建立方法:

自動推導型別建立slice

s1 := [] int {1, 2, 3, 4} 建立 有 4 個元素的切片,分別為:1234

藉助make建立 slice,格式:make(切片型別,長度,容量)

s2 := make([]int, 5, 10) len(s2) = 5, cap(s2) = 10

make時,沒有指定容量,那麼 長度==容量

s3 := make([]int, 5) len(s3) = 5, cap(s3) = 5

func main() {

s1 := [] int {1, 2, 3, 4} // 建立 有4個元素的切片

fmt。Println(“s1=”, s1)

s2 := make([]int, 5, 10) // 藉助make建立 slice,格式:make(切片型別,長度,容量)

s2[4] = 7

//s2[5] = 9 // 報錯:panic: runtime error: index out of range

fmt。Println(“s2=”, s2)

fmt。Printf(“len(s2)=%d, cap(s2)=%d\n”, len(s2), cap(s2))

s3 := make([]int, 5) // make時,沒指定容量,那麼 長度 == 容量

s3[2] = 3

fmt。Println(“s3=”, s3)

fmt。Printf(“len(s2)=%d, cap(s2)=%d\n”, len(s3), cap(s3))

}

注意:make只能建立slice、map和channel,並且返回一個有初始值(非零)的物件。

1。 切片Slice

需要說明,slice 並不是陣列或陣列指標。它透過內部指標和相關屬性引用陣列片段,以實現變長方案。

1。 切片:切片是陣列的一個引用,因此切片是引用型別。但自身是結構體,值複製傳遞。

2。 切片的長度可以改變,因此,切片是一個可變的陣列。

3。 切片遍歷方式和陣列一樣,可以用len()求長度。表示可用元素數量,讀寫操作不能超過該限制。

4。 cap可以求出slice最大擴張容量,不能超出陣列限制。0 <= len(slice) <= len(array),其中array是slice引用的陣列。

5。 切片的定義:var 變數名 []型別,比如 var str []string var arr []int。

6。 如果 slice == nil,那麼 len、cap 結果都等於 0。

1。1。1。 建立切片的各種方式

package main

import “fmt”

func main() {

//1。宣告切片

var s1 []int

if s1 == nil {

fmt。Println(“是空”)

} else {

fmt。Println(“不是空”)

}

// 2。:=

s2 := []int{}

// 3。make()

var s3 []int = make([]int, 0)

fmt。Println(s1, s2, s3)

// 4。初始化賦值

var s4 []int = make([]int, 0, 0)

fmt。Println(s4)

s5 := []int{1, 2, 3}

fmt。Println(s5)

// 5。從陣列切片

arr := [5]int{1, 2, 3, 4, 5}

var s6 []int

// 前包後不包

s6 = arr[1:4]

fmt。Println(s6)

}

1。1。2。 切片初始化

全域性:

var arr = [。。。]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

var slice0 []int = arr[start:end]

var slice1 []int = arr[:end]

var slice2 []int = arr[start:]

var slice3 []int = arr[:]

var slice4 = arr[:len(arr)-1] //去掉切片的最後一個元素

區域性:

arr2 := [。。。]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}

slice5 := arr[start:end]

slice6 := arr[:end]

slice7 := arr[start:]

slice8 := arr[:]

slice9 := arr[:len(arr)-1] //去掉切片的最後一個元素

golang2021資料格式(18)切片

程式碼:

package main

import (

“fmt”

var arr = [。。。]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

var slice0 []int = arr[2:8]

var slice1 []int = arr[0:6] //可以簡寫為 var slice []int = arr[:end]

var slice2 []int = arr[5:10] //可以簡寫為 var slice[]int = arr[start:]

var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]

var slice4 = arr[:len(arr)-1] //去掉切片的最後一個元素

func main() {

fmt。Printf(“全域性變數:arr %v\n”, arr)

fmt。Printf(“全域性變數:slice0 %v\n”, slice0)

fmt。Printf(“全域性變數:slice1 %v\n”, slice1)

fmt。Printf(“全域性變數:slice2 %v\n”, slice2)

fmt。Printf(“全域性變數:slice3 %v\n”, slice3)

fmt。Printf(“全域性變數:slice4 %v\n”, slice4)

fmt。Printf(“——————————————————-\n”)

arr2 := [。。。]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}

slice5 := arr[2:8]

slice6 := arr[0:6] //可以簡寫為 slice := arr[:end]

slice7 := arr[5:10] //可以簡寫為 slice := arr[start:]

slice8 := arr[0:len(arr)] //slice := arr[:]

slice9 := arr[:len(arr)-1] //去掉切片的最後一個元素

fmt。Printf(“區域性變數: arr2 %v\n”, arr2)

fmt。Printf(“區域性變數: slice5 %v\n”, slice5)

fmt。Printf(“區域性變數: slice6 %v\n”, slice6)

fmt。Printf(“區域性變數: slice7 %v\n”, slice7)

fmt。Printf(“區域性變數: slice8 %v\n”, slice8)

fmt。Printf(“區域性變數: slice9 %v\n”, slice9)

}

輸出結果:

全域性變數:arr [0 1 2 3 4 5 6 7 8 9]

全域性變數:slice0 [2 3 4 5 6 7]

全域性變數:slice1 [0 1 2 3 4 5]

全域性變數:slice2 [5 6 7 8 9]

全域性變數:slice3 [0 1 2 3 4 5 6 7 8 9]

全域性變數:slice4 [0 1 2 3 4 5 6 7 8]

——————————————————-

區域性變數: arr2 [9 8 7 6 5 4 3 2 1 0]

區域性變數: slice5 [2 3 4 5 6 7]

區域性變數: slice6 [0 1 2 3 4 5]

區域性變數: slice7 [5 6 7 8 9]

區域性變數: slice8 [0 1 2 3 4 5 6 7 8 9]

區域性變數: slice9 [0 1 2 3 4 5 6 7 8]

1。1。3。 透過make來建立切片

var slice []type = make([]type, len)

slice := make([]type, len)

slice := make([]type, len, cap)

golang2021資料格式(18)切片

程式碼:

package main

import (

“fmt”

var slice0 []int = make([]int, 10)

var slice1 = make([]int, 10)

var slice2 = make([]int, 10, 10)

func main() {

fmt。Printf(“make全域性slice0 :%v\n”, slice0)

fmt。Printf(“make全域性slice1 :%v\n”, slice1)

fmt。Printf(“make全域性slice2 :%v\n”, slice2)

fmt。Println(“————————————————————”)

slice3 := make([]int, 10)

slice4 := make([]int, 10)

slice5 := make([]int, 10, 10)

fmt。Printf(“make區域性slice3 :%v\n”, slice3)

fmt。Printf(“make區域性slice4 :%v\n”, slice4)

fmt。Printf(“make區域性slice5 :%v\n”, slice5)

}

輸出結果:

make全域性slice0 :[0 0 0 0 0 0 0 0 0 0]

make全域性slice1 :[0 0 0 0 0 0 0 0 0 0]

make全域性slice2 :[0 0 0 0 0 0 0 0 0 0]

————————————————————

make區域性slice3 :[0 0 0 0 0 0 0 0 0 0]

make區域性slice4 :[0 0 0 0 0 0 0 0 0 0]

make區域性slice5 :[0 0 0 0 0 0 0 0 0 0]

切片的記憶體佈局

golang2021資料格式(18)切片

讀寫操作實際目標是底層陣列,只需注意索引號的差別。

package main

import (

“fmt”

func main() {

data := [。。。]int{0, 1, 2, 3, 4, 5}

s := data[2:4]

s[0] += 100

s[1] += 200

fmt。Println(s)

fmt。Println(data)

}

輸出:

[102 203]

[0 1 102 203 4 5]

可直接建立 slice 物件,自動分配底層陣列。

package main

import “fmt”

func main() {

s1 := []int{0, 1, 2, 3, 8: 100} // 透過初始化表示式構造,可使用索引號。

fmt。Println(s1, len(s1), cap(s1))

s2 := make([]int, 6, 8) // 使用 make 建立,指定 len 和 cap 值。

fmt。Println(s2, len(s2), cap(s2))

s3 := make([]int, 6) // 省略 cap,相當於 cap = len。

fmt。Println(s3, len(s3), cap(s3))

}

輸出結果:

[0 1 2 3 0 0 0 0 100] 9 9

[0 0 0 0 0 0] 6 8

[0 0 0 0 0 0] 6 6

使用 make 動態建立slice,避免了陣列必須用常量做長度的麻煩。還可用指標直接訪問底層陣列,退化成普通陣列操作。

package main

import “fmt”

func main() {

s := []int{0, 1, 2, 3}

p := &s[2] // *int, 獲取底層陣列元素指標。

*p += 100

fmt。Println(s)

}

輸出結果:

[0 1 102 3]

至於 [][]T,是指元素型別為 []T 。

package main

import (

“fmt”

func main() {

data := [][]int{

[]int{1, 2, 3},

[]int{100, 200},

[]int{11, 22, 33, 44},

}

fmt。Println(data)

}

輸出結果:

[[1 2 3] [100 200] [11 22 33 44]]

可直接修改 struct array/slice 成員。

package main

import (

“fmt”

func main() {

d := [5]struct {

x int

}{}

s := d[:]

d[1]。x = 10

s[2]。x = 20

fmt。Println(d)

fmt。Printf(“%p, %p\n”, &d, &d[0])

}

輸出結果:

[{0} {10} {20} {0} {0}]

0xc4200160f0, 0xc4200160f0

1。1。4。 用append內建函式操作切片(切片追加)

package main

import (

“fmt”

func main() {

var a = []int{1, 2, 3}

fmt。Printf(“slice a : %v\n”, a)

var b = []int{4, 5, 6}

fmt。Printf(“slice b : %v\n”, b)

c := append(a, b。。。)

fmt。Printf(“slice c : %v\n”, c)

d := append(c, 7)

fmt。Printf(“slice d : %v\n”, d)

e := append(d, 8, 9, 10)

fmt。Printf(“slice e : %v\n”, e)

}

輸出結果:

slice a : [1 2 3]

slice b : [4 5 6]

slice c : [1 2 3 4 5 6]

slice d : [1 2 3 4 5 6 7]

slice e : [1 2 3 4 5 6 7 8 9 10]

append :向 slice 尾部新增資料,返回新的 slice 物件。

package main

import (

“fmt”

func main() {

s1 := make([]int, 0, 5)

fmt。Printf(“%p\n”, &s1)

s2 := append(s1, 1)

fmt。Printf(“%p\n”, &s2)

fmt。Println(s1, s2)

}

輸出結果:

0xc42000a060

0xc42000a080

[] [1]

1。1。5。 超出原 slice。cap 限制,就會重新分配底層陣列,即便原陣列並未填滿。

package main

import (

“fmt”

func main() {

data := [。。。]int{0, 1, 2, 3, 4, 10: 0}

s := data[:2:3]

s = append(s, 100, 200) // 一次 append 兩個值,超出 s。cap 限制。

fmt。Println(s, data) // 重新分配底層陣列,與原陣列無關。

fmt。Println(&s[0], &data[0]) // 比對底層陣列起始指標。

}

輸出結果:

[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]

0xc4200160f0 0xc420070060

從輸出結果可以看出,append 後的 s 重新分配了底層陣列,並複製資料。如果只追加一個值,則不會超過 s。cap 限制,也就不會重新分配。 通常以 2 倍容量重新分配底層陣列。在大批次新增資料時,建議一次性分配足夠大的空間,以減少記憶體分配和資料複製開銷。或初始化足夠長的 len 屬性,改用索引號進行操作。及時釋放不再使用的 slice 物件,避免持有過期陣列,造成 GC 無法回收。

1。1。6。 slice中cap重新分配規律:

package main

import (

“fmt”

func main() {

s := make([]int, 0, 1)

c := cap(s)

for i := 0; i < 50; i++ {

s = append(s, i)

if n := cap(s); n > c {

fmt。Printf(“cap: %d -> %d\n”, c, n)

c = n

}

}

}

輸出結果:

cap: 1 -> 2

cap: 2 -> 4

cap: 4 -> 8

cap: 8 -> 16

cap: 16 -> 32

cap: 32 -> 64

1。1。7。 切片複製

package main

import (

“fmt”

func main() {

s1 := []int{1, 2, 3, 4, 5}

fmt。Printf(“slice s1 : %v\n”, s1)

s2 := make([]int, 10)

fmt。Printf(“slice s2 : %v\n”, s2)

copy(s2, s1)

fmt。Printf(“copied slice s1 : %v\n”, s1)

fmt。Printf(“copied slice s2 : %v\n”, s2)

s3 := []int{1, 2, 3}

fmt。Printf(“slice s3 : %v\n”, s3)

s3 = append(s3, s2。。。)

fmt。Printf(“appended slice s3 : %v\n”, s3)

s3 = append(s3, 4, 5, 6)

fmt。Printf(“last slice s3 : %v\n”, s3)

}

輸出結果:

slice s1 : [1 2 3 4 5]

slice s2 : [0 0 0 0 0 0 0 0 0 0]

copied slice s1 : [1 2 3 4 5]

copied slice s2 : [1 2 3 4 5 0 0 0 0 0]

slice s3 : [1 2 3]

appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]

last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]

copy :函式 copy 在兩個 slice 間複製資料,複製長度以 len 小的為準。兩個 slice 可指向同一底層陣列,允許元素區間重疊。

package main

import (

“fmt”

func main() {

data := [。。。]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

fmt。Println(“array data : ”, data)

s1 := data[8:]

s2 := data[:5]

fmt。Printf(“slice s1 : %v\n”, s1)

fmt。Printf(“slice s2 : %v\n”, s2)

copy(s2, s1)

fmt。Printf(“copied slice s1 : %v\n”, s1)

fmt。Printf(“copied slice s2 : %v\n”, s2)

fmt。Println(“last array data : ”, data)

}

輸出結果:

array data : [0 1 2 3 4 5 6 7 8 9]

slice s1 : [8 9]

slice s2 : [0 1 2 3 4]

copied slice s1 : [8 9]

copied slice s2 : [8 9 2 3 4]

last array data : [8 9 2 3 4 5 6 7 8 9]

應及時將所需資料 copy 到較小的 slice,以便釋放超大號底層陣列記憶體。

1。1。8。 slice遍歷:

package main

import (

“fmt”

func main() {

data := [。。。]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

slice := data[:]

for index, value := range slice {

fmt。Printf(“inde : %v , value : %v\n”, index, value)

}

}

輸出結果:

inde : 0 , value : 0

inde : 1 , value : 1

inde : 2 , value : 2

inde : 3 , value : 3

inde : 4 , value : 4

inde : 5 , value : 5

inde : 6 , value : 6

inde : 7 , value : 7

inde : 8 , value : 8

inde : 9 , value : 9

1。1。9。 切片resize(調整大小)

package main

import (

“fmt”

func main() {

var a = []int{1, 3, 4, 5}

fmt。Printf(“slice a : %v , len(a) : %v\n”, a, len(a))

b := a[1:2]

fmt。Printf(“slice b : %v , len(b) : %v\n”, b, len(b))

c := b[0:3]

fmt。Printf(“slice c : %v , len(c) : %v\n”, c, len(c))

}

輸出結果:

slice a : [1 3 4 5] , len(a) : 4

slice b : [3] , len(b) : 1

slice c : [3 4 5] , len(c) : 3

1。1。10。 陣列和切片的記憶體佈局

golang2021資料格式(18)切片

1。1。11。 字串和切片(string and slice)

string底層就是一個byte的陣列,因此,也可以進行切片操作。

package main

import (

“fmt”

func main() {

str := “hello world”

s1 := str[0:5]

fmt。Println(s1)

s2 := str[6:]

fmt。Println(s2)

}

輸出結果:

hello

world

string本身是不可變的,因此要改變string中字元。需要如下操作: 英文字串:

package main

import (

“fmt”

func main() {

str := “Hello world”

s := []byte(str) //中文字元需要用[]rune(str)

s[6] = ‘G’

s = s[:8]

s = append(s, ‘!’)

str = string(s)

fmt。Println(str)

}

輸出結果:

Hello Go!

1。1。12。 含有中文字串:

package main

import (

“fmt”

func main() {

str := “你好,世界!hello world!”

s := []rune(str)

s[3] = ‘夠’

s[4] = ‘浪’

s[12] = ‘g’

s = s[:14]

str = string(s)

fmt。Println(str)

}

輸出結果:

你好,夠浪!hello go

golang slice data[:6:8] 兩個冒號的理解

常規slice , data[6:8],從第6位到第8位(返回6, 7),長度len為2, 最大可擴充長度cap為4(6-9)

另一種寫法: data[:6:8] 每個數字前都有個冒號, slice內容為data從0到第6位,長度len為6,最大擴充項cap設定為8

a[x:y:z] 切片內容 [x:y] 切片長度: y-x 切片容量:z-x

package main

import (

“fmt”

func main() {

slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

d1 := slice[6:8]

fmt。Println(d1, len(d1), cap(d1))

d2 := slice[:6:8]

fmt。Println(d2, len(d2), cap(d2))

}

陣列or切片轉字串:

strings。Replace(strings。Trim(fmt。Sprint(array_or_slice), “[]”), “ ”, “,”, -1)

golang2021資料格式(18)切片