Go數(shù)據(jù)-切片(三)

切片(slice)本身并非動(dòng)態(tài)數(shù)組或數(shù)組指針踩官。它內(nèi)部通過指針引用底層數(shù)組瞬逊,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定區(qū)域轨香。
切片(slice)是建立在數(shù)組之上的更方便专普,更靈活,更強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)弹沽。切片并不存儲(chǔ)任何元素而只是對(duì)現(xiàn)有數(shù)組的引用。

創(chuàng)建切片

元素類型為 T 的切片表示為: [ ]T筋粗。

func main() {
    a := [5]int{76, 77, 78, 79, 80}
    var b []int = a[1:4] //creates a slice from a[1] to a[3]
    fmt.Println(b)
}

通過 a[start:end] 這樣的語法創(chuàng)建了一個(gè)從 a[start]a[end -1] 的切片策橘。在上面的案例中, a[1:4] 創(chuàng)建了一個(gè)從 a[1]a[3] 的切片娜亿。因此 b 的值為:[77 78 79]丽已。
另外一個(gè)創(chuàng)建方式

func main() {
    c := []int{6, 7, 8} //creates and array and returns a slice reference
    fmt.Println(c)
}

在上面的案例中,c := []int{6, 7, 8} 創(chuàng)建了一個(gè)長度為 3 的 int 數(shù)組买决,并返回一個(gè)切片給 c沛婴。

修改切片

切片本身不包含任何數(shù)據(jù)吼畏。它僅僅是底層數(shù)組的一個(gè)上層表示。對(duì)切片進(jìn)行的任何修改都將反映在底層數(shù)組中嘁灯。

func main() {
    arr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
    slice := arr[2:5]
    fmt.Println("array before",arr)
    for i := range slice {
        slice[i]++
    }
    fmt.Println("array after",arr)
}

我們創(chuàng)建了一個(gè)從 arr[2]arr[5] 的切片 slice泻蚊。for 循環(huán)將這些元素值加 1。執(zhí)行完 for 語句之后打印原數(shù)組的值丑婿,我們可以看到原數(shù)組的值被改變了性雄。程序輸出如下:

array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]

當(dāng)若干個(gè)切片共享同一個(gè)底層數(shù)組時(shí),對(duì)每一個(gè)切片的修改都會(huì)反映在底層數(shù)組中羹奉。

func main() {
    numa := [3]int{78, 79 ,80}
    nums1 := numa[:] //creates a slice which contains all elements of the array
    nums2 := numa[:]
    fmt.Println("array before change 1", numa)
    nums1[0] = 100
    fmt.Println("array after modification to slice nums1", numa)
    nums2[1] = 101
    fmt.Println("array after modification to slice nums2", numa)
}

numa[:] 中缺少了開始和結(jié)束的索引值秒旋,這種情況下開始和結(jié)束的索引值默認(rèn)為 0len(numa)。這里 nums1nums2 共享了同一個(gè)數(shù)組诀拭。輸出為:

array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]

切片的長度和容量

切片的長度是指切片中元素的個(gè)數(shù)迁筛。切片的容量是指從切片的起始元素開始到其底層數(shù)組中的最后一個(gè)元素的個(gè)數(shù)。(使用內(nèi)置函數(shù) cap 返回切片的容量)

func main() {
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice))
}

在上面的案例中耕挨,創(chuàng)建了一個(gè)以 fruitarray 為底層數(shù)組细卧,索引從 13 的切片 fruitslice。因此 fruitslice 長度為2俗孝。
fruitarray 的長度是 7酒甸。fruiteslice 是從 fruitarray 的索引 1 開始的。因此 fruiteslice 的容量是從 fruitarray 的第 1 個(gè)元素開始算起的數(shù)組中的元素個(gè)數(shù)赋铝,這個(gè)值是 6插勤。因此 fruitslice 的容量是 6。輸出為:length of slice 2 capacity 6革骨。

切片的長度可以動(dòng)態(tài)的改變(最大為其容量)农尖。任何超出最大容量的操作都會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤。

func main() {
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
    fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
    fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}

在上面的案例中良哲, 修改 fruitslice 的長度為它的容量盛卡。輸出如下:

length of slice 2 capacity 6
After re-slicing length is 6 and capacity is 6

用 make 創(chuàng)建切片

內(nèi)置函數(shù) func make([]T, len, cap) []T 可以用來創(chuàng)建切片,該函數(shù)接受長度和容量作為參數(shù)筑凫,返回切片滑沧。容量是可選的,默認(rèn)與長度相同巍实。使用 make 函數(shù)將會(huì)創(chuàng)建一個(gè)數(shù)組并返回它的切片滓技。

func main() {
    i := make([]int, 5, 5)
    fmt.Println(i)
}

make 創(chuàng)建的切片的元素值默認(rèn)為 0 值。上面的程序輸出為:[0 0 0 0 0]棚潦。

追加元素到切片

我們已經(jīng)知道數(shù)組是固定長度的庐冯,它們的長度不能動(dòng)態(tài)增加仑最。而切片是動(dòng)態(tài)的酬核,可以使用內(nèi)置函數(shù) append 添加元素到切片盅惜。append 的函數(shù)原型為:append(s []T, x ...T) []T。
x …T 表示 append 函數(shù)可以接受的參數(shù)個(gè)數(shù)是可變的。這種函數(shù)叫做變參函數(shù)

你可能會(huì)問一個(gè)問題:如果切片是建立在數(shù)組之上的,而數(shù)組本身不能改變長度收叶,那么切片是如何動(dòng)態(tài)改變長度的呢?實(shí)際發(fā)生的情況是玄组,當(dāng)新元素通過調(diào)用 append 函數(shù)追加到切片末尾時(shí)滔驾,如果超出了容量,append 內(nèi)部會(huì)創(chuàng)建一個(gè)新的數(shù)組俄讹。并將原有數(shù)組的元素被拷貝給這個(gè)新的數(shù)組哆致,最后返回建立在這個(gè)新數(shù)組上的切片。這個(gè)新切片的容量是舊切片的二倍(當(dāng)超出切片的容量時(shí)患膛,append 將會(huì)在其內(nèi)部創(chuàng)建新的數(shù)組摊阀,該數(shù)組的大小是原切片容量的 2 倍。最后 append 返回這個(gè)數(shù)組的全切片踪蹬,即從 0 到 length - 1 的切片)胞此。下面的程序使事情變得明朗:

func main() {
    cars := []string{"Ferrari", "Honda", "Ford"}
    fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
    cars = append(cars, "Toyota")
    fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}

在上面的程序中,cars 的容量開始時(shí)為 3跃捣。我們追加了一個(gè)新的元素給 cars漱牵,并將 append(cars, "Toyota")的返回值重新復(fù)制給 cars。現(xiàn)在 cars 的容量翻倍疚漆,變?yōu)?6酣胀。上面的程序輸出為:

cars: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6

切片的 0 值為 nil。一個(gè) nil 切片的長度和容量都為 0娶聘∥畔猓可以利用 append 函數(shù)給一個(gè) nil 切片追加值。

func main() {
    var names []string //zero value of a slice is nil
    if names == nil {
        fmt.Println("slice is nil going to append")
        names = append(names, "John", "Sebastian", "Vinay")
        fmt.Println("names contents:",names)
    }
}

在上面的程序中 namesnil丸升,并且我們把 3 個(gè)字符串追加給 names铆农。輸出為:

slice is nil going to append  
names contents: [John Sebastian Vinay]

可以使用 ... 操作符將一個(gè)切片追加到另一個(gè)切片末尾:

func main() {
    veggies := []string{"potatoes", "tomatoes", "brinjal"}
    fruits := []string{"oranges", "apples"}
    food := append(veggies, fruits...)
    fmt.Println("food:", food)
}

上面的程序中,在第10行將 fruits 追加到 veggies 并賦值給 food狡耻。...操作符用來展開切片墩剖。程序的輸出為:food: [potatoes tomatoes brinjal oranges apples]

切片作為函數(shù)參數(shù)

可以認(rèn)為切片在內(nèi)部表示為如下的結(jié)構(gòu)體:

type slice struct {  
    Length        int
    Capacity      int
    ZerothElement *byte
}

可以看到切片包含長度夷狰、容量涛碑、以及一個(gè)指向首元素的指針。當(dāng)將一個(gè)切片作為參數(shù)傳遞給一個(gè)函數(shù)時(shí)孵淘,雖然是值傳遞,但是指針始終指向同一個(gè)數(shù)組歹篓。因此將切片作為參數(shù)傳給函數(shù)時(shí)瘫证,函數(shù)對(duì)該切片的修改在函數(shù)外部也可以看到揉阎。讓我們寫一個(gè)程序來驗(yàn)證這一點(diǎn)。

func subtactOne(numbers []int) {
    for i := range numbers {
        numbers[i] -= 2
    }

}
func main() {
    nos := []int{8, 7, 6}
    fmt.Println("slice before function call", nos)
    subtactOne(nos)                               //function modifies the slice
    fmt.Println("slice after function call", nos) //modifications are visible outside

}

在上面的程序中背捌,將切片中的每個(gè)元素的值減2毙籽。在函數(shù)調(diào)用之后打印切片的的內(nèi)容,發(fā)現(xiàn)切片內(nèi)容發(fā)生了改變毡庆。你可以回想一下坑赡,這不同于一個(gè)數(shù)組,對(duì)函數(shù)內(nèi)部的數(shù)組所做的更改在函數(shù)外不可見么抗。上面的程序輸出如下:

slice before function call [8 7 6]
slice after function call [6 5 4]

多維切片

同數(shù)組一樣毅否,切片也可以有多個(gè)維度。

func main() {
    pls := [][]string {
        {"C", "C++"},
        {"JavaScript"},
        {"Go", "Rust"},
    }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

輸出:

C C++ 
JavaScript 
Go Rust 

內(nèi)存優(yōu)化

切片保留對(duì)底層數(shù)組的引用蝇刀。只要切片存在于內(nèi)存中螟加,數(shù)組就不能被垃圾回收。這在內(nèi)存管理方便可能是值得關(guān)注的吞琐。假設(shè)我們有一個(gè)非常大的數(shù)組捆探,而我們只需要處理它的一小部分,為此我們創(chuàng)建這個(gè)數(shù)組的一個(gè)切片站粟,并處理這個(gè)切片黍图。這里要注意的事情是,數(shù)組仍然存在于內(nèi)存中奴烙,因?yàn)榍衅谝盟?/p>

解決該問題的一個(gè)方法是使用 copy 函數(shù) func copy(dst, src []T) int 來創(chuàng)建該切片的一個(gè)拷貝助被。這樣我們就可以使用這個(gè)新的切片,原來的數(shù)組可以被垃圾回收缸沃。

func countries() []string {
    countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
    neededCountries := countries[:len(countries)-2]
    countriesCpy := make([]string, len(neededCountries))
    copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
    return countriesCpy
}
func main() {
    countriesNeeded := countries()
    fmt.Println(countriesNeeded)
}

在上面程序中恰起,neededCountries := countries[:len(countries)-2] 創(chuàng)建一個(gè)底層數(shù)組為 countries 并排除最后兩個(gè)元素的切片。將 neededCountries 拷貝到 countriesCpy 并在下一行返回 countriesCpy≈耗粒現(xiàn)在數(shù)組countries 可以被垃圾回收检盼,因?yàn)?neededCountries 不再被引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翘单,一起剝皮案震驚了整個(gè)濱河市吨枉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哄芜,老刑警劉巖貌亭,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異认臊,居然都是意外死亡圃庭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剧腻,“玉大人拘央,你說我怎么就攤上這事∈樵冢” “怎么了灰伟?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長儒旬。 經(jīng)常有香客問我栏账,道長,這世上最難降的妖魔是什么栈源? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任挡爵,我火速辦了婚禮,結(jié)果婚禮上凉翻,老公的妹妹穿的比我還像新娘了讨。我一直安慰自己,他們只是感情好制轰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布前计。 她就那樣靜靜地躺著,像睡著了一般垃杖。 火紅的嫁衣襯著肌膚如雪男杈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天调俘,我揣著相機(jī)與錄音伶棒,去河邊找鬼。 笑死彩库,一個(gè)胖子當(dāng)著我的面吹牛肤无,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骇钦,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼宛渐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了眯搭?” 一聲冷哼從身側(cè)響起窥翩,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳞仙,沒想到半個(gè)月后寇蚊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棍好,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年仗岸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了允耿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爹梁,死狀恐怖右犹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情姚垃,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布盼忌,位于F島的核電站积糯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谦纱。R本人自食惡果不足惜看成,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望跨嘉。 院中可真熱鬧川慌,春花似錦、人聲如沸祠乃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亮瓷。三九已至琴拧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘱支,已是汗流浹背蚓胸。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留除师,地道東北人沛膳。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像汛聚,于是被迫代替她去往敵國和親锹安。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容