溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

go語言切片怎么生成

發(fā)布時(shí)間:2022-12-27 13:46:08 來源:億速云 閱讀:100 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“go語言切片怎么生成”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

在go語言中,切片(slice)是對數(shù)組的一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類型,這個(gè)片段可以是整個(gè)數(shù)組,也可以是由起始和終止索引標(biāo)識的一些項(xiàng)的子集;切片的內(nèi)存分布是連續(xù)的,所以可以把切片當(dāng)做一個(gè)大小不固定的數(shù)組。切片有三個(gè)字段的數(shù)據(jù)結(jié)構(gòu):指向底層數(shù)組的指針、切片訪問的元素的個(gè)數(shù)(即長度)和切片允許增長到的元素個(gè)數(shù)(即容量)。

切片(slice)是對數(shù)組的一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類型(因此更類似于 C/C++ 中的數(shù)組類型,或者 Python 中的 list 類型),這個(gè)片段可以是整個(gè)數(shù)組,也可以是由起始和終止索引標(biāo)識的一些項(xiàng)的子集,需要注意的是,終止索引標(biāo)識的項(xiàng)不包括在切片內(nèi)。

Go語言中切片的內(nèi)部結(jié)構(gòu)包含地址、大小和容量,切片一般用于快速地操作一塊數(shù)據(jù)集合,如果將數(shù)據(jù)集合比作切糕的話,切片就是你要的“那一塊”,切的過程包含從哪里開始(切片的起始位置)及切多大(切片的大?。?,容量可以理解為裝切片的口袋大小,如下圖所示。

go語言切片怎么生成
圖:切片結(jié)構(gòu)和內(nèi)存分配

切片的內(nèi)存分布是連續(xù)的,所以你可以把切片當(dāng)做一個(gè)大小不固定的數(shù)組。

切片有三個(gè)字段的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)包含 Go 語言需要操作底層數(shù)組的元數(shù)據(jù),這 3 個(gè)字段分別是指向底層數(shù)組的指針、切片訪問的元素的個(gè)數(shù)(即長度)和切片允許增長到的元素個(gè)數(shù)(即容量)。后面會進(jìn)一步講解長度和容量的區(qū)別。

go語言切片怎么生成

從數(shù)組或切片生成新的切片

切片默認(rèn)指向一段連續(xù)內(nèi)存區(qū)域,可以是數(shù)組,也可以是切片本身。

從連續(xù)內(nèi)存區(qū)域生成切片是常見的操作,格式如下:

slice [開始位置 : 結(jié)束位置]

語法說明如下:

  • slice:表示目標(biāo)切片對象;

  • 開始位置:對應(yīng)目標(biāo)切片對象的索引;

  • 結(jié)束位置:對應(yīng)目標(biāo)切片的結(jié)束索引。

從數(shù)組生成切片,代碼如下:

var a  = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])

其中 a 是一個(gè)擁有 3 個(gè)整型元素的數(shù)組,被初始化為數(shù)值 1 到 3,使用 a[1:2] 可以生成一個(gè)新的切片,代碼運(yùn)行結(jié)果如下:

[1 2 3]  [2]

其中 [2] 就是 a[1:2] 切片操作的結(jié)果。

從數(shù)組或切片生成新的切片擁有如下特性:

  • 取出的元素?cái)?shù)量為:結(jié)束位置 - 開始位置;

  • 取出元素不包含結(jié)束位置對應(yīng)的索引,切片最后一個(gè)元素使用 slice[len(slice)] 獲?。?/p>

  • 當(dāng)缺省開始位置時(shí),表示從連續(xù)區(qū)域開頭到結(jié)束位置;

  • 當(dāng)缺省結(jié)束位置時(shí),表示從開始位置到整個(gè)連續(xù)區(qū)域末尾;

  • 兩者同時(shí)缺省時(shí),與切片本身等效;

  • 兩者同時(shí)為 0 時(shí),等效于空切片,一般用于切片復(fù)位。

根據(jù)索引位置取切片 slice 元素值時(shí),取值范圍是(0~len(slice)-1),超界會報(bào)運(yùn)行時(shí)錯(cuò)誤,生成切片時(shí),結(jié)束位置可以填寫 len(slice) 但不會報(bào)錯(cuò)。

下面通過實(shí)例來熟悉切片的特性。

1) 從指定范圍中生成切片

切片和數(shù)組密不可分,如果將數(shù)組理解為一棟辦公樓,那么切片就是把不同的連續(xù)樓層出租給使用者,出租的過程需要選擇開始樓層和結(jié)束樓層,這個(gè)過程就會生成切片,示例代碼如下:

var highRiseBuilding [30]int
for i := 0; i < 30; i++ {
        highRiseBuilding[i] = i + 1
}
// 區(qū)間
fmt.Println(highRiseBuilding[10:15])
// 中間到尾部的所有元素
fmt.Println(highRiseBuilding[20:])
// 開頭到中間指定位置的所有元素
fmt.Println(highRiseBuilding[:2])

代碼輸出如下:

go語言切片怎么生成

代碼中構(gòu)建了一個(gè) 30 層的高層建筑,數(shù)組的元素值從 1 到 30,分別代表不同的獨(dú)立樓層,輸出的結(jié)果是不同的租售方案。

代碼說明如下:

  • 第 8 行,嘗試出租一個(gè)區(qū)間樓層。

  • 第 11 行,出租 20 層以上。

  • 第 14 行,出租 2 層以下,一般是商用鋪面。

切片有點(diǎn)像C語言里的指針,指針可以做運(yùn)算,但代價(jià)是內(nèi)存操作越界,切片在指針的基礎(chǔ)上增加了大小,約束了切片對應(yīng)的內(nèi)存區(qū)域,切片使用中無法對切片內(nèi)部的地址和大小進(jìn)行手動調(diào)整,因此切片比指針更安全、強(qiáng)大。

2) 表示原有的切片

生成切片的格式中,當(dāng)開始和結(jié)束位置都被忽略時(shí),生成的切片將表示和原切片一致的切片,并且生成的切片與原切片在數(shù)據(jù)內(nèi)容上也是一致的,代碼如下:

a := []int{1, 2, 3}
fmt.Println(a[:])

a 是一個(gè)擁有 3 個(gè)元素的切片,將 a 切片使用 a[:] 進(jìn)行操作后,得到的切片與 a 切片一致,代碼輸出如下:

[1 2 3]

3) 重置切片,清空擁有的元素

把切片的開始和結(jié)束位置都設(shè)為 0 時(shí),生成的切片將變空,代碼如下:

a := []int{1, 2, 3}
fmt.Println(a[0:0])

代碼輸出如下:

go語言切片怎么生成

直接聲明新的切片

除了可以從原有的數(shù)組或者切片中生成切片外,也可以聲明一個(gè)新的切片,每一種類型都可以擁有其切片類型,表示多個(gè)相同類型元素的連續(xù)集合,因此切片類型也可以被聲明,切片類型聲明格式如下:

var name []Type

其中 name 表示切片的變量名,Type 表示切片對應(yīng)的元素類型。

下面代碼展示了切片聲明的使用過程:

// 聲明字符串切片
var strList []string

// 聲明整型切片
var numList []int

// 聲明一個(gè)空切片
var numListEmpty = []int{}

// 輸出3個(gè)切片
fmt.Println(strList, numList, numListEmpty)

// 輸出3個(gè)切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))

// 切片判定空的結(jié)果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)

代碼輸出結(jié)果:

go語言切片怎么生成

代碼說明如下:

  • 第 2 行,聲明一個(gè)字符串切片,切片中擁有多個(gè)字符串。

  • 第 5 行,聲明一個(gè)整型切片,切片中擁有多個(gè)整型數(shù)值。

  • 第 8 行,將 numListEmpty 聲明為一個(gè)整型切片,本來會在{}中填充切片的初始化元素,這里沒有填充,所以切片是空的,但是此時(shí)的 numListEmpty 已經(jīng)被分配了內(nèi)存,只是還沒有元素。

  • 第 11 行,切片均沒有任何元素,3 個(gè)切片輸出元素內(nèi)容均為空。

  • 第 14 行,沒有對切片進(jìn)行任何操作,strList 和 numList 沒有指向任何數(shù)組或者其他切片。

  • 第 17 行和第 18 行,聲明但未使用的切片的默認(rèn)值是 nil,strList 和 numList 也是 nil,所以和 nil 比較的結(jié)果是 true。

  • 第 19 行,numListEmpty 已經(jīng)被分配到了內(nèi)存,但沒有元素,因此和 nil 比較時(shí)是 false。

切片是動態(tài)結(jié)構(gòu),只能與 nil 判定相等,不能互相判定相等。聲明新的切片后,可以使用 append() 函數(shù)向切片中添加元素。

使用 make() 函數(shù)構(gòu)造切片

如果需要動態(tài)地創(chuàng)建一個(gè)切片,可以使用 make() 內(nèi)建函數(shù),格式如下:

make( []Type, size, cap )

其中 Type 是指切片的元素類型,size 指的是為這個(gè)類型分配多少個(gè)元素,cap 為預(yù)分配的元素?cái)?shù)量,這個(gè)值設(shè)定后不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。

示例如下:

a := make([]int, 2)
b := make([]int, 2, 10)

fmt.Println(a, b)
fmt.Println(len(a), len(b))

代碼輸出如下:

go語言切片怎么生成

其中 a 和 b 均是預(yù)分配 2 個(gè)元素的切片,只是 b 的內(nèi)部存儲空間已經(jīng)分配了 10 個(gè),但實(shí)際使用了 2 個(gè)元素。

容量不會影響當(dāng)前的元素個(gè)數(shù),因此 a 和 b 取 len 都是 2。

溫馨提示

使用 make() 函數(shù)生成的切片一定發(fā)生了內(nèi)存分配操作,但給定開始與結(jié)束位置(包括切片復(fù)位)的切片只是將新的切片結(jié)構(gòu)指向已經(jīng)分配好的內(nèi)存區(qū)域,設(shè)定開始與結(jié)束位置,不會發(fā)生內(nèi)存分配操作。

切片的使用

切片的使用和數(shù)組是一模一樣的:

func main() {
    slice1 := []int{1,2,3,4}
    fmt.Println(slice1[1])
}

切片創(chuàng)建切片

切片之所以稱為切片,是因?yàn)樗皇菍?yīng)底層數(shù)組的一部分,看如下所示代碼:

func main() {
    slice := []int{10, 20, 30, 40, 50}
    newSlice := slice[1:3]
}

為了說明上面的代碼,我們看下面的這張圖:
go語言切片怎么生成

第一個(gè)切片slice 能夠看到底層數(shù)組全部5 個(gè)元素的容量,不過之后的newSlice 就看不到。對于newSlice,底層數(shù)組的容量只有4 個(gè)元素。newSlice 無法訪問到它所指向的底層數(shù)組的第一個(gè)元素之前的部分。所以,對newSlice 來說,之前的那些元素就是不存在的。

需要記住的是,現(xiàn)在兩個(gè)切片共享同一個(gè)底層數(shù)組。如果一個(gè)切片修改了該底層數(shù)組的共享部分,另一個(gè)切片也能感知到,運(yùn)行下面的代碼:

func main() {
    slice := []int{10, 20, 30, 40, 50}
    newSlice := slice[1:3]

    slice[1] = 200
    fmt.Println(newSlice[0])
}

運(yùn)行結(jié)果如下:

200

切片只能訪問到其長度內(nèi)的元素。試圖訪問超出其長度的元素將會導(dǎo)致語言運(yùn)行時(shí)異常,比如對上面的newSlice,他只能訪問索引為1和2的元素(不包括3),比如:

func main() {
    slice := []int{10, 20, 30, 40, 50}
    newSlice := slice[1:3]

    fmt.Println(newSlice[3])
}

運(yùn)行代碼,控制臺會報(bào)錯(cuò):

panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
    E:/go-source/go-arr/main.go:20 +0x11

子切片的容量

我們知道切片可以再生出切片,那么子切片的容量為多大呢?我們來測試一下:

func main() {
    slice := make([]int, 2, 10)
    slice1 := slice[1:2]
    fmt.Println(cap(slice1))
}

控制臺打印結(jié)果為:

9
9

從結(jié)果我們可以推測,子切片的容量為底層數(shù)組的長度減去切片在底層數(shù)組的開始偏移量,比如在上面的例子中,slice1的偏移值為1,底層數(shù)組的大小為10,所以兩者相減,得到結(jié)果9。

向切片中追加元素

go提供了append方法用于向切片中追加元素,如下所示:

func main() {
    slice := make([]int, 2, 10)
    slice1 := slice[1:2]
    slice2 := append(slice1, 1)
    slice2[0] = 10001
    fmt.Println(slice)
    fmt.Println(cap(slice2))
}

輸出結(jié)果如下:

[0 10001]
9

此時(shí)slice,slice1,slice2共享底層數(shù)組,所以只要一個(gè)切片改變了某一個(gè)索引的值,會影響到所有的切片,還有一點(diǎn)值得注意,就是slice2的容量為9,記住這個(gè)值。

為了說明問題,我把例子改為如下所示代碼:

func main() {
    slice := make([]int, 2, 10)
    slice1 := slice[1:2]
    slice2 := append(slice1, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2[0] = 10001
    fmt.Println(slice)
    fmt.Println(slice1)
    fmt.Println(cap(slice2))
}

此時(shí)我們再次打印結(jié)果,神奇的事情出現(xiàn)了:

[0 0]
[0]
18

雖然我們改變0位置的值,但是并沒有影響到原來的slice和slice1,這是為啥呢?我們知道原始的slice2對應(yīng)的底層數(shù)組的容量為9,經(jīng)過我們一系列的append操作,原始的底層數(shù)組已經(jīng)無法容納更多的元素了,此時(shí)Go會分配另外一塊內(nèi)存,把原始切片從位置1開始的內(nèi)存復(fù)制到新的內(nèi)存地址中,也就是說現(xiàn)在的slice2切片對應(yīng)的底層數(shù)組和slice切片對應(yīng)的底層數(shù)組完全不是在同一個(gè)內(nèi)存地址,所以當(dāng)你此時(shí)更改slice2中的元素時(shí),對slice已經(jīng)來說,一點(diǎn)兒關(guān)系都沒有。

另外根據(jù)上面的打印結(jié)果,你也應(yīng)該猜到了,當(dāng)切片容量不足的時(shí)候,Go會以原始切片容量的2倍建立新的切片,在我們的例子中2*9=18,就是這么粗暴。

如何創(chuàng)建子切片時(shí)指定容量

在前面的例子中,我們創(chuàng)建子切片的時(shí)候,沒有指定子切片的容量,所以子切片的容量和我們上面討論的計(jì)算子切片的容量方法相等,那么我們?nèi)绾问謩又付ㄗ忧衅娜萘磕兀?/p>

在這里我們借用《Go實(shí)戰(zhàn)》中的一個(gè)例子:

func main() {
    source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
    slice := source[2:3:4]
    fmt.Println(cap(slice))
}

如果你仔細(xì)看的話,上面的子切片的生成方式和普通的切片有所不同,[]里面有三個(gè)部分組成,,第一個(gè)值表示新切片開始元素的索引位置,這個(gè)例子中是2。第二個(gè)值表示開始的索引位置(2)加上希望包括的元素的個(gè)數(shù)(1),2+1 的結(jié)果是3,所以第二個(gè)值就是3。為了設(shè)置容量,從索引位置2 開始,加上希望容量中包含的元素的個(gè)數(shù)(2),就得到了第三個(gè)值4。所以這個(gè)新的切片slice的長度為1,容量為2。還有一點(diǎn)大家一定要記住,你指定的容量不能比原先的容量,這里就是source的容量大,加入我們這樣設(shè)置的話:

func main() {
    source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
    slice := source[2:3:10]
    fmt.Println(cap(slice))
}

運(yùn)行結(jié)果如下,報(bào)錯(cuò)了,哈哈:

panic: runtime error: slice bounds out of range [::10] with capacity 5

goroutine 1 [running]:
main.main()
    E:/learn-go/slice/main.go:7 +0x1d

迭代切片

關(guān)于如何迭代切片,我們可以使用range配置來使用,如下:

func main() {
    slice:=[]int{1,2,4,6}
    for _, value:=range slice{
        fmt.Println(value)
    }
}

關(guān)于迭代切片,大家有一點(diǎn)需要注意,就以上面的例子為例,value只是slice中元素的副本,為啥呢?我們來驗(yàn)證這一點(diǎn):

func main() {
    slice:=[]int{1,2,4,6}
    for index, value:=range slice{
        fmt.Printf("value[%d],indexAddr:[%X],valueAddr:[%X],sliceAddr:[%X]\n",value,&index,&value,&slice[index])
    }
}

控制臺打印結(jié)果如下:

value[1],indexAddr:[C00000A0B8],valueAddr:[C00000A0D0],sliceAddr:[C000010380]
value[2],indexAddr:[C00000A0B8],valueAddr:[C00000A0D0],sliceAddr:[C000010388]
value[4],indexAddr:[C00000A0B8],valueAddr:[C00000A0D0],sliceAddr:[C000010390]
value[6],indexAddr:[C00000A0B8],valueAddr:[C00000A0D0],sliceAddr:[C000010398]

從上面的結(jié)果可以看到index和value的地址始終是不變的,所以它們始終是同一個(gè)變量,只是變量引用地址的內(nèi)容發(fā)生了變化,從而驗(yàn)證迭代的時(shí)候,只能是切片元素的副本,最后看看sliceAddr代表的地址相隔8個(gè)字節(jié),因?yàn)樵?4位系統(tǒng)上,每一個(gè)int類型的大小為8個(gè)字節(jié)。

函數(shù)間傳遞切片

函數(shù)間傳遞切片,也是以值的方式傳遞的,但是你還記得這篇博文開頭給出的切片的布局么?
go語言切片怎么生成
切片由三個(gè)部分組成,包括指向底層數(shù)組的指針,當(dāng)前切片的長度,當(dāng)前切片的容量,所以切片本身并不大,我們來測試一個(gè)切片的大?。?/p>

func main() {
    slice:=[]int{1,2,4,6}
    fmt.Println(unsafe.Sizeof(slice))
}

測試結(jié)果為:

24

也就是這個(gè)slice切片的大小為24字節(jié),所以當(dāng)切片作為參數(shù)傳遞的時(shí)候,幾乎沒有性能開銷,還有很重要的一點(diǎn),參數(shù)生成的副本的地址指針和原始切片的地址指針是一樣的,因此,如果你在函數(shù)里面修改了切片,那么會影響到原始的切片,我們來驗(yàn)證這點(diǎn):

func main() {
    slice:=[]int{1,2,4,6}
    handleSlice(slice)
    fmt.Println(slice)
}

打印結(jié)果:

[100 2 4 6]

“go語言切片怎么生成”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI