十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
Go 中的分片數(shù)組,實(shí)際上有點(diǎn)類似于Java中的ArrayList,是一個(gè)可以擴(kuò)展的數(shù)組,但是Go中的切片由比較靈活,它和數(shù)組很像,也是基于數(shù)組,所以在了解Go切片前我們先了解下數(shù)組。
創(chuàng)新互聯(lián)長(zhǎng)期為1000多家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為雙城企業(yè)提供專業(yè)的成都做網(wǎng)站、成都網(wǎng)站建設(shè),雙城網(wǎng)站改版等技術(shù)服務(wù)。擁有十年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
數(shù)組簡(jiǎn)單描述就由相同類型元素組成的數(shù)據(jù)結(jié)構(gòu), 在創(chuàng)建初期就確定了長(zhǎng)度,是不可變的。
但是Go的數(shù)組類型又和C與Java的數(shù)組類型不一樣, NewArray 用于創(chuàng)建一個(gè)數(shù)組,從源碼中可以看出最后返回的是 Array{}的指針,并不是第一個(gè)元素的指針,在Go中數(shù)組屬于值類型,在進(jìn)行傳遞時(shí),采取的是值傳遞,通過拷貝整個(gè)數(shù)組。Go語言的數(shù)組是一種有序的struct。
Go 語言的數(shù)組有兩種不同的創(chuàng)建方式,一種是顯示的初始化,一種是隱式的初始化。
注意一定是使用 [...]T 進(jìn)行創(chuàng)建,使用三個(gè)點(diǎn)的隱式創(chuàng)建,編譯器會(huì)對(duì)數(shù)組的大小進(jìn)行推導(dǎo),只是Go提供的一種語法糖。
其次,Go中數(shù)組的類型,是由數(shù)值類型和長(zhǎng)度兩個(gè)一起確定的。[2]int 和 [3]int 不是同一個(gè)類型,不能進(jìn)行傳參和比較,把數(shù)組理解為類型和長(zhǎng)度兩個(gè)屬性的結(jié)構(gòu)體,其實(shí)就一目了然了。
Go中的數(shù)組屬于值類型,通常應(yīng)該存儲(chǔ)于棧中,局部變量依然會(huì)根據(jù)逃逸分析確定存儲(chǔ)棧還是堆中。
編譯器對(duì)數(shù)組函數(shù)中做兩種不同的優(yōu)化:
在靜態(tài)區(qū)完成賦值后復(fù)制到棧中。
總結(jié)起來,在不考慮逃逸分析的情況下,如果數(shù)組中元素的個(gè)數(shù)小于或者等于 4 個(gè),那么所有的變量會(huì)直接在棧上初始化,如果數(shù)組元素大于 4 個(gè),變量就會(huì)在靜態(tài)存儲(chǔ)區(qū)初始化然后拷貝到棧上。
由于數(shù)組是值類型,那么賦值和函數(shù)傳參操作都會(huì)復(fù)制整個(gè)數(shù)組數(shù)據(jù)。
不管是賦值或函數(shù)傳參,地址都不一致,發(fā)生了拷貝。如果數(shù)組的數(shù)據(jù)較大,則會(huì)消耗掉大量?jī)?nèi)存。那么為了減少拷貝我們可以主動(dòng)的傳遞指針呀。
地址是一樣的,不過傳指針會(huì)有一個(gè)弊端,從打印結(jié)果可以看到,指針地址都是同一個(gè),萬一原數(shù)組的指針指向更改了,那么函數(shù)里面的指針指向都會(huì)跟著更改。
同樣的我們將數(shù)組轉(zhuǎn)換為切片,通過傳遞切片,地址是不一樣的,數(shù)組值相同。
切片是引用傳遞,所以它們不需要使用額外的內(nèi)存并且比使用數(shù)組更有效率。
所以,切片屬于引用類型。
通過這種方式可以將數(shù)組轉(zhuǎn)換為切片。
中間不加三個(gè)點(diǎn)就是切片,使用這種方式創(chuàng)建切片,實(shí)際上是先創(chuàng)建數(shù)組,然后再通過第一種方式創(chuàng)建。
使用make創(chuàng)建切片,就不光編譯期了,make創(chuàng)建切片會(huì)涉及到運(yùn)行期。1. 切片的大小和容量是否足夠??;
切片是否發(fā)生了逃逸,最終在堆上初始化。如果切片小的話會(huì)先在棧或靜態(tài)區(qū)進(jìn)行創(chuàng)建。
切片有一個(gè)數(shù)組的指針,len是指切片的長(zhǎng)度, cap指的是切片的容量。
cap是在初始化切片是生成的容量。
發(fā)現(xiàn)切片的結(jié)構(gòu)體是數(shù)組的地址指針array unsafe.Pointer,而Go中數(shù)組的地址代表數(shù)組結(jié)構(gòu)體的地址。
slice 中得到一塊內(nèi)存地址,array[0]或者unsafe.Pointer(array[0])。
也可以通過地址構(gòu)造切片
nil切片:指的unsafe.Pointer 為nil
空切片:
創(chuàng)建的指針不為空,len和cap為空
當(dāng)一個(gè)切片的容量滿了,就需要擴(kuò)容了。怎么擴(kuò),策略是什么?
如果原來數(shù)組切片的容量已經(jīng)達(dá)到了最大值,再想擴(kuò)容, Go 默認(rèn)會(huì)先開一片內(nèi)存區(qū)域,把原來的值拷貝過來,然后再執(zhí)行 append() 操作。這種情況對(duì)現(xiàn)數(shù)組的地址和原數(shù)組地址不相同。
從上面結(jié)果我們可以看到,如果用 range 的方式去遍歷一個(gè)切片,拿到的 Value 其實(shí)是切片里面的值拷貝,即淺拷貝。所以每次打印 Value 的地址都不變。
由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達(dá)不到更改原切片值的目的的,需要通過 slice[index] 獲取真實(shí)的地址。
注:本文是對(duì) golang-101-hacks 中文翻譯。
在Go語言中,函數(shù)參數(shù)是值傳遞。使用slice作為函數(shù)參數(shù)時(shí),函數(shù)獲取到的是slice的副本:一個(gè)指針,指向底層數(shù)組的起始地址,同時(shí)帶有slice的長(zhǎng)度和容量。既然各位熟知數(shù)據(jù)存儲(chǔ)的內(nèi)存的地址,現(xiàn)在可以對(duì)切片數(shù)據(jù)進(jìn)行修改。讓我們看看下面的例子:
In Go, the function parameters are passed by value. With respect to use slice as a function argument, that means the function will get the copies of the slice: a pointer which points to the starting address of the underlying array, accompanied by the length and capacity of the slice. Oh boy! Since you know the address of the memory which is used to store the data, you can tweak the slice now. Let's see the following example:
運(yùn)行結(jié)果如下
由此可見,執(zhí)行modifyValue函數(shù),切片s的元素發(fā)生了變化。盡管modifyValue函數(shù)只是操作slice的副本,但是任然改變了切片的數(shù)據(jù)元素,看另一個(gè)例子:
You can see, after running modifyValue function, the content of slice s is changed. Although the modifyValue function just gets a copy of the memory address of slice's underlying array, it is enough!
See another example:
The result is like this:
而這一次,addValue函數(shù)并沒有修改main函數(shù)中的切片s的元素。這是因?yàn)樗皇遣僮髑衅瑂的副本,而不是切片s本身。所以如果真的想讓函數(shù)改變切片的內(nèi)容,可以傳遞切片的地址:
This time, the addValue function doesn't take effect on the s slice in main function. That's because it just manipulate the copy of the s, not the "real" s.
So if you really want the function to change the content of a slice, you can pass the address of the slice:
運(yùn)行結(jié)果如下
append的第二個(gè)參數(shù)是可變長(zhǎng)參數(shù),你這樣寫1,2 就會(huì)追加2個(gè)元素這符合預(yù)期效果
、數(shù)組
與其他大多數(shù)語言類似,Go語言的數(shù)組也是一個(gè)元素類型相同的定長(zhǎng)的序列。
(1)數(shù)組的創(chuàng)建。
數(shù)組有3種創(chuàng)建方式:[length]Type 、[N]Type{value1, value2, ... , valueN}、[...]Type{value1, value2, ... , valueN} 如下:
復(fù)制代碼代碼如下:
func test5() {
var iarray1 [5]int32
var iarray2 [5]int32 = [5]int32{1, 2, 3, 4, 5}
iarray3 := [5]int32{1, 2, 3, 4, 5}
iarray4 := [5]int32{6, 7, 8, 9, 10}
iarray5 := [...]int32{11, 12, 13, 14, 15}
iarray6 := [4][4]int32{{1}, {1, 2}, {1, 2, 3}}
fmt.Println(iarray1)
fmt.Println(iarray2)
fmt.Println(iarray3)
fmt.Println(iarray4)
fmt.Println(iarray5)
fmt.Println(iarray6)
}
結(jié)果:
[0 0 0 0 0]
[1 2 3 4 5]
[1 2 3 4 5]
[6 7 8 9 10]
[11 12 13 14 15]
[[1 0 0 0] [1 2 0 0] [1 2 3 0] [0 0 0 0]]
我們看數(shù)組 iarray1,只聲明,并未賦值,Go語言幫我們自動(dòng)賦值為0。再看 iarray2 和 iarray3 ,我們可以看到,Go語言的聲明,可以表明類型,也可以不表明類型,var iarray3 = [5]int32{1, 2, 3, 4, 5} 也是完全沒問題的。
(2)數(shù)組的容量和長(zhǎng)度是一樣的。cap() 函數(shù)和 len() 函數(shù)均輸出數(shù)組的容量(即長(zhǎng)度)。如:
復(fù)制代碼代碼如下:
func test6() {
iarray4 := [5]int32{6, 7, 8, 9, 10}
fmt.Println(len(iarray4))
fmt.Println(cap(iarray4))
}
輸出都是5。
(3)使用:
復(fù)制代碼代碼如下:
func test7() {
iarray7 := [5]string{"aaa", `bb`, "可以啦", "叫我說什么好", "()"}
fmt.Println(iarray7)
for i := range iarray7 {
fmt.Println(iarray7[i])
}
}
二、切片
Go語言中,切片是長(zhǎng)度可變、容量固定的相同的元素序列。Go語言的切片本質(zhì)是一個(gè)數(shù)組。容量固定是因?yàn)閿?shù)組的長(zhǎng)度是固定的,切片的容量即隱藏?cái)?shù)組的長(zhǎng)度。長(zhǎng)度可變指的是在數(shù)組長(zhǎng)度的范圍內(nèi)可變。
(1)切片的創(chuàng)建。
切片的創(chuàng)建有4種方式:
1)make ( []Type ,length, capacity )
2) make ( []Type, length)
3) []Type{}
4) []Type{value1 , value2 , ... , valueN }
從3)、4)可見,創(chuàng)建切片跟創(chuàng)建數(shù)組唯一的區(qū)別在于 Type 前的“ [] ”中是否有數(shù)字,為空,則代表切片,否則則代表數(shù)組。因?yàn)榍衅情L(zhǎng)度可變的。如下是創(chuàng)建切片的示例:
復(fù)制代碼代碼如下:
func test8() {
slice1 := make([]int32, 5, 8)
slice2 := make([]int32, 9)
slice3 := []int32{}
slice4 := []int32{1, 2, 3, 4, 5}
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
fmt.Println(slice4)
}
輸出為:
[0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[]
[1 2 3 4 5]
如上,創(chuàng)造了4個(gè)切片,3個(gè)空切片,一個(gè)有值的切片。
(2)切片與隱藏?cái)?shù)組:
一個(gè)切片是一個(gè)隱藏?cái)?shù)組的引用,并且對(duì)于該切片的切片也引用同一個(gè)數(shù)組。如下示例,創(chuàng)建了一個(gè)切片slice0,并根據(jù)這個(gè)切片創(chuàng)建了2個(gè)切片 slice1 和 slice2:
復(fù)制代碼代碼如下:
func test9() {
slice0 := []string{"a", "b", "c", "d", "e"}
slice1 := slice0[2 : len(slice0)-1]
slice2 := slice0[:3]
fmt.Println(slice0, slice1, slice2)
slice2[2] = "8"
fmt.Println(slice0, slice1, slice2)
}
輸出為:
[a b c d e] [c d] [a b c]
[a b 8 d e] [8 d] [a b 8]
可見,切片slice0 、 slice1 和 slice2是同一個(gè)底層數(shù)組的引用,所以slice2改變了,其他兩個(gè)都會(huì)變。
(3)遍歷、修改切片:
復(fù)制代碼代碼如下:
func test10() {
slice0 := []string{"a", "b", "c", "d", "e"}
fmt.Println("\n~~~~~~元素遍歷~~~~~~")
for _, ele := range slice0 {
fmt.Print(ele, " ")
ele = "7"
}
fmt.Println("\n~~~~~~索引遍歷~~~~~~")
for index := range slice0 {
fmt.Print(slice0[index], " ")
}
fmt.Println("\n~~~~~~元素索引共同使用~~~~~~")
for index, ele := range slice0 {
fmt.Print(ele, slice0[index], " ")
}
fmt.Println("\n~~~~~~修改~~~~~~")
for index := range slice0 {
slice0[index] = "9"
}
fmt.Println(slice0)
}
如上,前三種循環(huán)使用了不同的for range循環(huán),當(dāng)for后面,range前面有2個(gè)元素時(shí),第一個(gè)元素代表索引,第二個(gè)元素代表元素值,使用 “_” 則表示忽略,因?yàn)間o語言中,未使用的值會(huì)導(dǎo)致編譯錯(cuò)誤。
只有一個(gè)元素時(shí),該元素代表索引。
只有用索引才能修改元素。如在第一個(gè)遍歷中,賦值ele為7,結(jié)果沒有作用。因?yàn)樵谠乇闅v中,ele是值傳遞,ele是該切片元素的副本,修改它不會(huì)影響原本值,而在第四個(gè)遍歷——索引遍歷中,修改的是該切片元素引用的值,所以可以修改。
結(jié)果為:
~~~~~~元素遍歷~~~~~~
a b c d e
~~~~~~索引遍歷~~~~~~
a b c d e
~~~~~~元素索引共同使用~~~~~~
aa bb cc dd ee
~~~~~~修改~~~~~~
[9 9 9 9 9]
(4)、追加、復(fù)制切片:
復(fù)制代碼代碼如下:
func test11() {
slice := []int32{}
fmt.Printf("slice的長(zhǎng)度為:%d,slice為:%v\n", len(slice), slice)
slice = append(slice, 12, 11, 10, 9)
fmt.Printf("追加后,slice的長(zhǎng)度為:%d,slice為:%v\n", len(slice), slice)
slicecp := make([]int32, (len(slice)))
fmt.Printf("slicecp的長(zhǎng)度為:%d,slicecp為:%v\n", len(slicecp), slicecp)
copy(slicecp, slice)
fmt.Printf("復(fù)制賦值后,slicecp的長(zhǎng)度為:%d,slicecp為:%v\n", len(slicecp), slicecp)
}
追加、復(fù)制切片,用的是內(nèi)置函數(shù)append和copy,copy函數(shù)返回的是最后所復(fù)制的元素的數(shù)量。
(5)、內(nèi)置函數(shù)append
內(nèi)置函數(shù)append可以向一個(gè)切片后追加一個(gè)或多個(gè)同類型的其他值。如果追加的元素?cái)?shù)量超過了原切片容量,那么最后返回的是一個(gè)全新數(shù)組中的全新切片。如果沒有超過,那么最后返回的是原數(shù)組中的全新切片。無論如何,append對(duì)原切片無任何影響。如下示例:
復(fù)制代碼代碼如下:
func test12() {
slice := []int32{1, 2, 3, 4, 5, 6}
slice2 := slice[:2]
_ = append(slice2, 50, 60, 70, 80, 90)
fmt.Printf("slice為:%v\n", slice)
fmt.Printf("操作的切片:%v\n", slice2)
_ = append(slice2, 50, 60)
fmt.Printf("slice為:%v\n", slice)
fmt.Printf("操作的切片:%v\n", slice2)
}
如上,append方法用了2次,結(jié)果返回的結(jié)果完全不同,原因是第二次append方法追加的元素?cái)?shù)量沒有超過 slice 的容量。而無論怎樣,原切片slice2都無影響。結(jié)果:
slice為:[1 2 3 4 5 6]
操作的切片:[1 2]
slice為:[1 2 50 60 5 6]
操作的切片:[1 2]
定義一個(gè)切片,然后讓切片去引用一個(gè)已經(jīng)創(chuàng)建好的數(shù)組?;菊Z法如下:
索引1:切片引用的起始元素位
索引2:切片只引用該元素位之前的元素
例程如下:
在該方法中,我們未指定容量cap,這里的值為5是系統(tǒng)定義的。
在方法一中,可以用arr數(shù)組名來操控?cái)?shù)組中的元素,也可以通過slice切片來操控?cái)?shù)組中的元素。切片是直接引用數(shù)組,數(shù)組是事先存在的,程序員是可見的。
通過 make 來創(chuàng)建切片,基本語法如下:
make函數(shù)第三個(gè)參數(shù)cap即容量是可選的,如果一定要自己注明的話,要注意保證cap≥len。
用該方法可以 指定切片的大小(len)和容量(cap)
例程如下:
由于未賦值系統(tǒng)默認(rèn)將元素值置為0,即:
數(shù)值類型數(shù)組:????默認(rèn)值為 0
字符串?dāng)?shù)組:? ? ? ?默認(rèn)值為 ""
bool數(shù)組:? ? ? ? ? ?默認(rèn)值為 false
在方法二中,通過make方式創(chuàng)建的切片對(duì)應(yīng)的數(shù)組是由make底層維護(hù),對(duì)外不可見,即只能通過slice去訪問各個(gè)元素。
定義一個(gè)切片,直接就指定具體數(shù)組,使用原理類似于make的方式。
例程如下:
Go語言標(biāo)準(zhǔn)庫中提供了sort包對(duì)整型,浮點(diǎn)型,字符串型切片進(jìn)行排序,檢查一個(gè)切片是否排好序,使用二分法搜索函數(shù)在一個(gè)有序切片中搜索一個(gè)元素等功能。
關(guān)于sort包內(nèi)的函數(shù)說明與使用,請(qǐng)查看
在這里簡(jiǎn)單講幾個(gè)sort包中常用的函數(shù)
在Go語言中,對(duì)字符串的排序都是按照字節(jié)排序,也就是說在對(duì)字符串排序時(shí)是區(qū)分大小寫的。
二分搜索算法
Go語言中提供了一個(gè)使用二分搜索算法的sort.Search(size,fn)方法:每次只需要比較㏒?n個(gè)元素,其中n為切片中元素的總數(shù)。
sort.Search(size,fn)函數(shù)接受兩個(gè)參數(shù):所處理的切片的長(zhǎng)度和一個(gè)將目標(biāo)元素與有序切片的元素相比較的函數(shù),該函數(shù)是一個(gè)閉包,如果該有序切片是升序排列,那么在判斷時(shí)使用 有序切片的元素 = 目標(biāo)元素。該函數(shù)返回一個(gè)int值,表示與目標(biāo)元素相同的切片元素的索引。
在切片中查找出某個(gè)與目標(biāo)字符串相同的元素索引