【Go語言基礎】slice

快樂的提千萬發表於2023-02-21

一、概述

陣列(Array)的長度在定義之後無法再次修改;陣列是值型別,每次傳遞都將產生一份副本。

顯然這種資料結構無法完全滿足開發者的真實需求。Go語言提供了陣列切片(slice)來彌補陣列的不足。

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

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

【Go語言基礎】slice

二、基本語法

切片的建立和初始化

slice和陣列的區別:宣告陣列時,方括號內寫明瞭陣列的長度或使用...自動計算長度,而宣告slice時,方括號內沒有任何字元。

var s1 []int //宣告切片和宣告array一樣,只是少了長度,此為空(nil)切片
s2 := []int{}

//make([]T, length, capacity) //capacity省略,則和length的值相同
var s3 []int = make([]int, 0)
s4 := make([]int, 0, 0)

s5 := []int{1, 2, 3} //建立切片並初始化

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

// runtime/slice.go
type slice struct {
    array unsafe.Pointer // 元素指標
    len   int // 長度 
    cap   int // 容量
}

【引申1】[3]int 和 [4]int 是同一個型別嗎?

不是。因為陣列的長度是型別的一部分,這是與 slice 不同的一點。

cap和len的區別

簡單點說,len(sli)表示可見元素有幾個(即直接列印元素看到的元素個數),而cap(sli)表示所有元素有幾個。

比如:

arr := []int{2, 3, 5, 7, 11, 13}
sli := arr[1:4]
fmt.Println(sli) //[3 5 7]
fmt.Println(len(sli))//3
fmt.Println(cap(sli))//5

三、切片的操作

切片擷取

操作 含義
s[n] 切片s中索引位置為n的項
s[:] 從切片s的索引位置0到len(s)-1處所獲得的切片
s[low:] 從切片s的索引位置low到len(s)-1處所獲得的切片
s[:high] 從切片s的索引位置0到high處所獲得的切片,len=high
s[low:high] 從切片s的索引位置low到high處所獲得的切片,len=high-low
s[low:high:max] 從切片s的索引位置low到high處所獲得的切片,len=high-low,cap=max-low
len(s) 切片s的長度,總是<=cap(s)
cap(s) 切片s的容量,總是>=len(s)

示例說明:

array := []int

操作 結果 len cap 說明
array[:6:8] [0 1 2 3 4 5] 6 8 省略 low
array[5:] [5 6 7 8 9] 5 5 省略 high、 max
array[:3] [0 1 2] 3 10 省略 high、 max
array[:] [0 1 2 3 4 5 6 7 8 9] 10 10 全部省略
【Go語言基礎】slice 【Go語言基礎】slice

append

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

var s1 []int //建立nil切換
//s1 := make([]int, 0)
s1 = append(s1, 1)       //追加1個元素
s1 = append(s1, 2, 3)    //追加2個元素
s1 = append(s1, 4, 5, 6) //追加3個元素
fmt.Println(s1)          //[1 2 3 4 5 6]

s2 := make([]int, 5)
s2 = append(s2, 6)
fmt.Println(s2) //[0 0 0 0 0 6]

s3 := []int{1, 2, 3}
s3 = append(s3, 4, 5)
fmt.Println(s3)//[1 2 3 4 5]

append函式會智慧地底層陣列的容量增長,一旦超過原底層陣列容量,通常以2倍容量重新分配底層陣列,並複製原來的資料:

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
    */
}
【Go語言基礎】slice

copy

函式 copy 在兩個 slice 間複製資料,複製⻓度以 len 小的為準,兩個 slice 可指向同⼀底層陣列。

data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[8:]  //{8, 9}
s2 := data[:5] //{0, 1, 2, 3, 4}
copy(s2, s1)    // dst:s2, src:s1

fmt.Println(s2)   //[8 9 2 3 4]
fmt.Println(data) //[8 9 2 3 4 5 6 7 8 9]

注意要目的方需要先分配好空間,不然會丟失。
比如 源有5個,目的3個,則只會複製3個過來。

g.textConfigsCopy = make([]string, len(g.textConfigs))
copy(g.textConfigsCopy, g.textConfigs)

切片刪除元素

delete_index := 2
//檢視刪除位置之前的元素和 之後的元素
fmt.Println(slice[:delete_index], slice[delete_index+1:])
//將刪除點前後的元素連線起來
slice = append(slice[:delete_index], slice[delete_index+1:]...)
fmt.Println(slice)
【Go語言基礎】slice 【Go語言基礎】slice

四、slice對底層陣列的修改

package main

import "fmt"

func main() {
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := slice[2:5]
	fmt.Println(s1) //[2 3 20]

	s2 := s1[2:6:7] // 4,5,6,7
	s2 = append(s2, 100)
	s2 = append(s2, 200)
	fmt.Println(s2) //[4 5 6 7 100 200]

	s1[2] = 20//0, 1, 2, 3(賦值), 4, 5, 6, 7, 8, 9

	fmt.Println(slice) //[0 1 2 3 20 5 6 7 100 9]

//問題1:100怎麼來的。
//問題2:為什麼100後面是9不是200
}

列印一下看看

package main

import "fmt"

func main() {
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println(len(slice), cap(slice)) //10 10

	s1 := slice[2:5]
	fmt.Println(s1) //[2 3 20]
	fmt.Println(len(s1), cap(s1)) //3 8

	s2 := s1[2:6:7] // 4,5,6,7
	fmt.Println(len(s2), cap(s2)) // 4 5

	s2 = append(s2, 100)
	s2 = append(s2, 200)
	fmt.Println(s2) //[4 5 6 7 100 200]

	//0, 1, 2, 3(賦值), 4, 5, 6, 7, 8, 9

	s1[2] = 20

	fmt.Println(slice) //[0 1 2 3 20 5 6 7 100 9]
}
【Go語言基礎】slice

100怎麼來的:

【Go語言基礎】slice

100後面為什麼是9?

s2擴容了。

【Go語言基礎】slice

相關文章