本篇內(nèi)容介紹了“go語言切片怎么生成”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
洪雅ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
在go語言中,切片(slice)是對數(shù)組的一個連續(xù)片段的引用,所以切片是一個引用類型,這個片段可以是整個數(shù)組,也可以是由起始和終止索引標識的一些項的子集;切片的內(nèi)存分布是連續(xù)的,所以可以把切片當做一個大小不固定的數(shù)組。切片有三個字段的數(shù)據(jù)結(jié)構(gòu):指向底層數(shù)組的指針、切片訪問的元素的個數(shù)(即長度)和切片允許增長到的元素個數(shù)(即容量)。
切片(slice)是對數(shù)組的一個連續(xù)片段的引用,所以切片是一個引用類型(因此更類似于 C/C++ 中的數(shù)組類型,或者 Python 中的 list 類型),這個片段可以是整個數(shù)組,也可以是由起始和終止索引標識的一些項的子集,需要注意的是,終止索引標識的項不包括在切片內(nèi)。
Go語言中切片的內(nèi)部結(jié)構(gòu)包含地址、大小和容量,切片一般用于快速地操作一塊數(shù)據(jù)集合,如果將數(shù)據(jù)集合比作切糕的話,切片就是你要的“那一塊”,切的過程包含從哪里開始(切片的起始位置)及切多大(切片的大小),容量可以理解為裝切片的口袋大小,如下圖所示。
圖:切片結(jié)構(gòu)和內(nèi)存分配
切片的內(nèi)存分布是連續(xù)的,所以你可以把切片當做一個大小不固定的數(shù)組。
切片有三個字段的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)包含 Go 語言需要操作底層數(shù)組的元數(shù)據(jù),這 3 個字段分別是指向底層數(shù)組的指針、切片訪問的元素的個數(shù)(即長度)和切片允許增長到的元素個數(shù)(即容量)。后面會進一步講解長度和容量的區(qū)別。
切片默認指向一段連續(xù)內(nèi)存區(qū)域,可以是數(shù)組,也可以是切片本身。
從連續(xù)內(nèi)存區(qū)域生成切片是常見的操作,格式如下:
slice [開始位置 : 結(jié)束位置]
語法說明如下:
slice:表示目標切片對象;
開始位置:對應(yīng)目標切片對象的索引;
結(jié)束位置:對應(yīng)目標切片的結(jié)束索引。
從數(shù)組生成切片,代碼如下:
var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])
其中 a 是一個擁有 3 個整型元素的數(shù)組,被初始化為數(shù)值 1 到 3,使用 a[1:2] 可以生成一個新的切片,代碼運行結(jié)果如下:
[1 2 3] [2]
其中 [2] 就是 a[1:2] 切片操作的結(jié)果。
從數(shù)組或切片生成新的切片擁有如下特性:
取出的元素數(shù)量為:結(jié)束位置 - 開始位置;
取出元素不包含結(jié)束位置對應(yīng)的索引,切片最后一個元素使用 slice[len(slice)] 獲?。?/p>
當缺省開始位置時,表示從連續(xù)區(qū)域開頭到結(jié)束位置;
當缺省結(jié)束位置時,表示從開始位置到整個連續(xù)區(qū)域末尾;
兩者同時缺省時,與切片本身等效;
兩者同時為 0 時,等效于空切片,一般用于切片復(fù)位。
根據(jù)索引位置取切片 slice 元素值時,取值范圍是(0~len(slice)-1),超界會報運行時錯誤,生成切片時,結(jié)束位置可以填寫 len(slice) 但不會報錯。
下面通過實例來熟悉切片的特性。
1) 從指定范圍中生成切片
切片和數(shù)組密不可分,如果將數(shù)組理解為一棟辦公樓,那么切片就是把不同的連續(xù)樓層出租給使用者,出租的過程需要選擇開始樓層和結(jié)束樓層,這個過程就會生成切片,示例代碼如下:
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])
代碼輸出如下:
代碼中構(gòu)建了一個 30 層的高層建筑,數(shù)組的元素值從 1 到 30,分別代表不同的獨立樓層,輸出的結(jié)果是不同的租售方案。
代碼說明如下:
第 8 行,嘗試出租一個區(qū)間樓層。
第 11 行,出租 20 層以上。
第 14 行,出租 2 層以下,一般是商用鋪面。
切片有點像C語言里的指針,指針可以做運算,但代價是內(nèi)存操作越界,切片在指針的基礎(chǔ)上增加了大小,約束了切片對應(yīng)的內(nèi)存區(qū)域,切片使用中無法對切片內(nèi)部的地址和大小進行手動調(diào)整,因此切片比指針更安全、強大。
2) 表示原有的切片
生成切片的格式中,當開始和結(jié)束位置都被忽略時,生成的切片將表示和原切片一致的切片,并且生成的切片與原切片在數(shù)據(jù)內(nèi)容上也是一致的,代碼如下:
a := []int{1, 2, 3}
fmt.Println(a[:])
a 是一個擁有 3 個元素的切片,將 a 切片使用 a[:] 進行操作后,得到的切片與 a 切片一致,代碼輸出如下:
[1 2 3]
3) 重置切片,清空擁有的元素
把切片的開始和結(jié)束位置都設(shè)為 0 時,生成的切片將變空,代碼如下:
a := []int{1, 2, 3}
fmt.Println(a[0:0])
代碼輸出如下:
除了可以從原有的數(shù)組或者切片中生成切片外,也可以聲明一個新的切片,每一種類型都可以擁有其切片類型,表示多個相同類型元素的連續(xù)集合,因此切片類型也可以被聲明,切片類型聲明格式如下:
var name []Type
其中 name 表示切片的變量名,Type 表示切片對應(yīng)的元素類型。
下面代碼展示了切片聲明的使用過程:
// 聲明字符串切片
var strList []string
// 聲明整型切片
var numList []int
// 聲明一個空切片
var numListEmpty = []int{}
// 輸出3個切片
fmt.Println(strList, numList, numListEmpty)
// 輸出3個切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的結(jié)果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
代碼輸出結(jié)果:
代碼說明如下:
第 2 行,聲明一個字符串切片,切片中擁有多個字符串。
第 5 行,聲明一個整型切片,切片中擁有多個整型數(shù)值。
第 8 行,將 numListEmpty 聲明為一個整型切片,本來會在{}中填充切片的初始化元素,這里沒有填充,所以切片是空的,但是此時的 numListEmpty 已經(jīng)被分配了內(nèi)存,只是還沒有元素。
第 11 行,切片均沒有任何元素,3 個切片輸出元素內(nèi)容均為空。
第 14 行,沒有對切片進行任何操作,strList 和 numList 沒有指向任何數(shù)組或者其他切片。
第 17 行和第 18 行,聲明但未使用的切片的默認值是 nil,strList 和 numList 也是 nil,所以和 nil 比較的結(jié)果是 true。
第 19 行,numListEmpty 已經(jīng)被分配到了內(nèi)存,但沒有元素,因此和 nil 比較時是 false。
切片是動態(tài)結(jié)構(gòu),只能與 nil 判定相等,不能互相判定相等。聲明新的切片后,可以使用 append() 函數(shù)向切片中添加元素。
如果需要動態(tài)地創(chuàng)建一個切片,可以使用 make() 內(nèi)建函數(shù),格式如下:
make( []Type, size, cap )
其中 Type 是指切片的元素類型,size 指的是為這個類型分配多少個元素,cap 為預(yù)分配的元素數(shù)量,這個值設(shè)定后不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。
示例如下:
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
代碼輸出如下:
其中 a 和 b 均是預(yù)分配 2 個元素的切片,只是 b 的內(nèi)部存儲空間已經(jīng)分配了 10 個,但實際使用了 2 個元素。
容量不會影響當前的元素個數(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])
}
切片之所以稱為切片,是因為它只是對應(yīng)底層數(shù)組的一部分,看如下所示代碼:
func main() {
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
}
為了說明上面的代碼,我們看下面的這張圖:
第一個切片slice 能夠看到底層數(shù)組全部5 個元素的容量,不過之后的newSlice 就看不到。對于newSlice,底層數(shù)組的容量只有4 個元素。newSlice 無法訪問到它所指向的底層數(shù)組的第一個元素之前的部分。所以,對newSlice 來說,之前的那些元素就是不存在的。
需要記住的是,現(xiàn)在兩個切片共享同一個底層數(shù)組。如果一個切片修改了該底層數(shù)組的共享部分,另一個切片也能感知到,運行下面的代碼:
func main() {
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
slice[1] = 200
fmt.Println(newSlice[0])
}
運行結(jié)果如下:
200
切片只能訪問到其長度內(nèi)的元素。試圖訪問超出其長度的元素將會導(dǎo)致語言運行時異常,比如對上面的newSlice
,他只能訪問索引為1和2的元素(不包括3),比如:
func main() {
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
fmt.Println(newSlice[3])
}
運行代碼,控制臺會報錯:
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
此時slice,slice1,slice2共享底層數(shù)組,所以只要一個切片改變了某一個索引的值,會影響到所有的切片,還有一點值得注意,就是slice2的容量為9,記住這個值。
為了說明問題,我把例子改為如下所示代碼:
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))
}
此時我們再次打印結(jié)果,神奇的事情出現(xiàn)了:
[0 0]
[0]
18
雖然我們改變0位置的值,但是并沒有影響到原來的slice和slice1,這是為啥呢?我們知道原始的slice2對應(yīng)的底層數(shù)組的容量為9,經(jīng)過我們一系列的append操作,原始的底層數(shù)組已經(jīng)無法容納更多的元素了,此時Go會分配另外一塊內(nèi)存,把原始切片從位置1開始的內(nèi)存復(fù)制到新的內(nèi)存地址中,也就是說現(xiàn)在的slice2切片對應(yīng)的底層數(shù)組和slice切片對應(yīng)的底層數(shù)組完全不是在同一個內(nèi)存地址,所以當你此時更改slice2中的元素時,對slice已經(jīng)來說,一點兒關(guān)系都沒有。
另外根據(jù)上面的打印結(jié)果,你也應(yīng)該猜到了,當切片容量不足的時候,Go會以原始切片容量的2倍建立新的切片,在我們的例子中2*9=18,就是這么粗暴。
在前面的例子中,我們創(chuàng)建子切片的時候,沒有指定子切片的容量,所以子切片的容量和我們上面討論的計算子切片的容量方法相等,那么我們?nèi)绾问謩又付ㄗ忧衅娜萘磕兀?/p>
在這里我們借用《Go實戰(zhàn)》中的一個例子:
func main() {
source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
slice := source[2:3:4]
fmt.Println(cap(slice))
}
如果你仔細看的話,上面的子切片的生成方式和普通的切片有所不同,[]里面有三個部分組成,,第一個值表示新切片開始元素的索引位置,這個例子中是2。第二個值表示開始的索引位置(2)加上希望包括的元素的個數(shù)(1),2+1 的結(jié)果是3,所以第二個值就是3。為了設(shè)置容量,從索引位置2 開始,加上希望容量中包含的元素的個數(shù)(2),就得到了第三個值4。所以這個新的切片slice的長度為1,容量為2。還有一點大家一定要記住,你指定的容量不能比原先的容量,這里就是source的容量大,加入我們這樣設(shè)置的話:
func main() {
source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
slice := source[2:3:10]
fmt.Println(cap(slice))
}
運行結(jié)果如下,報錯了,哈哈:
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)于迭代切片,大家有一點需要注意,就以上面的例子為例,value只是slice中元素的副本,為啥呢?我們來驗證這一點:
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的地址始終是不變的,所以它們始終是同一個變量,只是變量引用地址的內(nèi)容發(fā)生了變化,從而驗證迭代的時候,只能是切片元素的副本,最后看看sliceAddr代表的地址相隔8個字節(jié),因為在64位系統(tǒng)上,每一個int類型的大小為8個字節(jié)。
函數(shù)間傳遞切片,也是以值的方式傳遞的,但是你還記得這篇博文開頭給出的切片的布局么?
切片由三個部分組成,包括指向底層數(shù)組的指針,當前切片的長度,當前切片的容量,所以切片本身并不大,我們來測試一個切片的大?。?/p>
func main() {
slice:=[]int{1,2,4,6}
fmt.Println(unsafe.Sizeof(slice))
}
測試結(jié)果為:
24
也就是這個slice切片的大小為24字節(jié),所以當切片作為參數(shù)傳遞的時候,幾乎沒有性能開銷,還有很重要的一點,參數(shù)生成的副本的地址指針和原始切片的地址指針是一樣的,因此,如果你在函數(shù)里面修改了切片,那么會影響到原始的切片,我們來驗證這點:
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)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!