golang中的slice

上文講解了數(shù)組這篇文章主要講解Slice(切片)。Slice代表變長的序列所意,其里面的每個元素都有相同的類型。Slice字面量為[]T其中T表示slice的類型,slice和數(shù)組的語法很像啊送,只是沒有固定長度。

1 創(chuàng)建slice

1.1 var slice []int

這種創(chuàng)建出來的 slice 是一個 nil slice欣孤。它的長度和容量都為0馋没。和nil比較的結(jié)果為true。

1.2 make創(chuàng)建

如果cap可以省略那len就等于cap降传。其中l(wèi)en可以為0表示這個slice的長度為0篷朵,容量為0婆排。

s1 := make([]int, len, cap)
1.3 new創(chuàng)建
s2 := new([]int)
1.4 字面量形式創(chuàng)建切片
s3 := []int{1, 2, 3, 4, 5, 6}
s4 := []int{}  //創(chuàng)建空切片
s5 := []string{99: 100}   //初始化第100個元素
1.5 基于數(shù)組創(chuàng)建數(shù)組切片
var array = [10]int{1, 2, 3, 4, 5, 6}
var s6  = array[1:4] //[2,3,4] 左閉右開
var s7  = array[4:] //[5,6,0,0,0,0] 
var s8 = array[2:4:6] //[3,4] len=2,cap=4 data[low, high, max] low表示索引開始處閉區(qū)間声旺,high表示len開區(qū)間,max表示容量開區(qū)間段只。

1.6 基于切片創(chuàng)建切片
slice := []int{1, 2, 3, 4, 5, 6}
s10 := slice[:4]  //beginIndex如果為空則表示從0開始
s11 := slice[4:]  //endIndex如果為空則表示到數(shù)組最后一個元素
var 12 = slice[2:4:6] //同1.5

2 底層數(shù)據(jù)結(jié)構(gòu)

一個slice是一個輕量級的數(shù)據(jù)結(jié)構(gòu)(結(jié)構(gòu)體)腮猖,提供了訪問數(shù)組的元素的功能。一個slice由3部分組成pointer,len,cap赞枕,其中pointer指向底層數(shù)組的地址(注意不一定是首地址)澈缺。以下是slice的定義:

#runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
#反射中的SliceHeader
#reflect/value.go
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

圖片示意圖表示slice中指針指向了數(shù)組首地址,len是4炕婶,cap是6谍椅。


圖1

3 共享底層數(shù)據(jù)

  • 多個slice之間可以共享底層的數(shù)據(jù),并且引用的數(shù)組部分區(qū)間可能重疊
func main()  {
    var array  = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var s1 = array[0:4]
    var s2 = array[0:3]
    fmt.Println(s1,s2)
}
//輸出結(jié)果:[1 2 3 4] [1 2 3] 表示1古话,2雏吭,3是引用相同的位置的數(shù)據(jù)。

圖2是示意圖:


圖2

圖3是gdb顯示的變量陪踩,內(nèi)存的值:


圖3

由此可見如果s1修改了共享的數(shù)據(jù)杖们,那s2的值也會改變悉抵。

4 append追加元素

函數(shù)原型:
func append(slice []Type, elems ...Type) []Type
參數(shù)elems參數(shù)可變,因此可以追加多個值到slice中摘完,還可以用...傳入一個切片姥饰。

slice := append(slice, elem1, elem2)
slice := append(slice, slice_other...)

append會返回新的slice,append返回值必須使用否則編譯器會報錯孝治。
追加元素是向底層數(shù)組中追加元素列粪,但是底層數(shù)組長度是固定的,如果數(shù)組已經(jīng)滿了就沒法添加了谈飒。這就會涉及到擴容問題了岂座。

var slice = make([]int, 6)
fmt.Printf("%p\n",slice)

sliceNew := append(slice, 7)
fmt.Printf("%p\n",sliceNew)
fmt.Println(cap(sliceNew))


//0xc420018150
//0xc420074060
//12

可以看到slice與sliceNew的地址不同說明sliceNew已經(jīng)遷移到別的地方了。在查看新slice的容量為12證明新的容量擴大了杭措,也就是說新slice預(yù)留了一些緩存费什,防止每次append都去遷移造成資源消耗。具體擴容多少可以查看大佬的文章深度解密Go語言之Slice--饒全成 [碼農(nóng)桃花源]

如果將var slice = make([]int, 6,)改成var slice = make([]int, 6, 7)手素,就是還有1個容量鸳址,那在運行此代碼發(fā)現(xiàn)slice與sliceNew是一樣的,沒有用遷移泉懦。

5 for range

for循環(huán)會對slice元素值一次拷貝到item稿黍。更改item中的值不會改變原slice的元素值。

slice := []int{1,2,3}
for _, item := range slice {
    item++
}
    fmt.Println(slice)
//output: [1,2,3]

6 函數(shù)傳參

函數(shù)傳slice是引用傳參崩哩,修改被調(diào)函數(shù)的值闻察,調(diào)用函數(shù)的slice也會改變。

func main()  {
    slice := []int{1,2,3}
    test(slice)
    fmt.Println(slice)
}
func test(a []int) {
    a[1] = 100
}
//output [1,100,3]

7 兩個slice不能用==比較

因為slice底層數(shù)據(jù)有可能變化琢锋。

8 make slice 匯編執(zhí)行過程

func main()  {
    s := make([]int, 3, 10)
    fmt.Println(s)
}

go tool compile -S run.go >> run.s
生成的匯編代碼如下:

1 "".main STEXT size=206 args=0x0 locals=0x58
2   0x0000 00000 (run.go:5) TEXT    "".main(SB), ABIInternal, $88-0   //為main函數(shù)分配棧幀大小為88B
3   0x0000 00000 (run.go:5) MOVQ    (TLS), CX
4   0x0009 00009 (run.go:5) CMPQ    SP, 16(CX)//是否需要擴容
5   0x000d 00013 (run.go:5) JLS 196 //跳轉(zhuǎn)到196處去擴容
6   0x0013 00019 (run.go:5) SUBQ    $88, SP  //將sp向低地址移動88B
7   0x0017 00023 (run.go:5) MOVQ    BP, 80(SP) //parent BP 緩存到80(sp)處
8   0x001c 00028 (run.go:5) LEAQ    80(SP), BP //將80(SP)處的地址存入BP寄存器中,這樣main函數(shù)的棧底設(shè)置完畢呢灶。
9   0x0021 00033 (run.go:5) FUNCDATA    $0, gclocals·69c1753bd5f81501d95132d08af04464(SB) //gc相關(guān)忽略
10  0x0021 00033 (run.go:5) FUNCDATA    $1, gclocals·568470801006e5c0dc3947ea998fe279(SB) //gc相關(guān)忽略
11  0x0021 00033 (run.go:5) FUNCDATA    $3, gclocals·bfec7e55b3f043d1941c093912808913(SB) //gc相關(guān)忽略
12  0x0021 00033 (run.go:5) FUNCDATA    $4, "".main.stkobj(SB) //gc相關(guān)忽略
13  0x0021 00033 (run.go:7) PCDATA  $2, $1 //gc相關(guān)忽略
14  0x0021 00033 (run.go:7) PCDATA  $0, $0 //gc相關(guān)忽略

//s := make([]int, 3, 10) make開始處
//將type.int(SB) =>[]int地址賦值給AX
15  0x0021 00033 (run.go:7) LEAQ    type.int(SB), AX
16  0x0028 00040 (run.go:7) PCDATA  $2, $0
將AX中的[]int地址移動到棧頂處(SP)
17  0x0028 00040 (run.go:7) MOVQ    AX, (SP)
//將參數(shù)3移動到離棧頂8個字節(jié)處
18  0x002c 00044 (run.go:7) MOVQ    $3, 8(SP)
//將參數(shù)10移動到離棧頂16個字節(jié)處
19  0x0035 00053 (run.go:7) MOVQ    $10, 16(SP)

//調(diào)用函數(shù)makeslice(SB)
//makeslice的原型如下:
//func makeslice(et *_type, len, cap int) slice 
//其中*_type 為slice類型吴超,len為長度,cap為容量鸯乃, slice為返回值
20  0x003e 00062 (run.go:7) CALL    runtime.makeslice(SB)

21  0x0043 00067 (run.go:7) PCDATA  $2, $1 //gc相關(guān)忽略
函數(shù)調(diào)用完之后的返回值slice
22  0x0043 00067 (run.go:7) MOVQ    24(SP), AX
23  0x0048 00072 (run.go:8) PCDATA  $2, $0
24  0x0048 00072 (run.go:8) MOVQ    AX, (SP)
25  0x004c 00076 (run.go:8) MOVQ    $3, 8(SP)
26  0x0055 00085 (run.go:8) MOVQ    $10, 16(SP)

  
//func convTslice(val []byte) (x unsafe.Pointer) 
//調(diào)用convTslice
27  0x005e 00094 (run.go:8) CALL    runtime.convTslice(SB)
28  0x0063 00099 (run.go:8) PCDATA  $2, $1
29  0x0063 00099 (run.go:8) MOVQ    24(SP), AX
30  0x0068 00104 (run.go:8) PCDATA  $0, $1
31  0x0068 00104 (run.go:8) XORPS   X0, X0
32  0x006b 00107 (run.go:8) MOVUPS  X0, ""..autotmp_11+64(SP)
33  0x0070 00112 (run.go:8) PCDATA  $2, $2
34  0x0070 00112 (run.go:8) LEAQ    type.[]int(SB), CX
35  0x0077 00119 (run.go:8) PCDATA  $2, $1
36  0x0077 00119 (run.go:8) MOVQ    CX, ""..autotmp_11+64(SP)
37  0x007c 00124 (run.go:8) PCDATA  $2, $0
38  0x007c 00124 (run.go:8) MOVQ    AX, ""..autotmp_11+72(SP)
39  0x0081 00129 (run.go:8) XCHGL   AX, AX

//以下是調(diào)用Println()詳見通過Println分析如何系統(tǒng)調(diào)用的

40  0x0082 00130 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
41  0x0082 00130 ($GOROOT/src/fmt/print.go:275) MOVQ    os.Stdout(SB), AX
42  0x0089 00137 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $2
43  0x0089 00137 ($GOROOT/src/fmt/print.go:275) LEAQ    go.itab.*os.File,io.Writer(SB), CX
44  0x0090 00144 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
45  0x0090 00144 ($GOROOT/src/fmt/print.go:275) MOVQ    CX, (SP)
46  0x0094 00148 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $0
47  0x0094 00148 ($GOROOT/src/fmt/print.go:275) MOVQ    AX, 8(SP)
48  0x0099 00153 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
49  0x0099 00153 ($GOROOT/src/fmt/print.go:275) PCDATA  $0, $0
50  0x0099 00153 ($GOROOT/src/fmt/print.go:275) LEAQ    ""..autotmp_11+64(SP), AX
51  0x009e 00158 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $0
52  0x009e 00158 ($GOROOT/src/fmt/print.go:275) MOVQ    AX, 16(SP)
53  0x00a3 00163 ($GOROOT/src/fmt/print.go:275) MOVQ    $1, 24(SP)
54  0x00ac 00172 ($GOROOT/src/fmt/print.go:275) MOVQ    $1, 32(SP)
55  0x00b5 00181 ($GOROOT/src/fmt/print.go:275) CALL    fmt.Fprintln(SB)


//恢復(fù)parent func stack 將緩存在80(SP)的parent BP恢復(fù)到BP
56  0x00ba 00186 (<unknown line number>)    MOVQ    80(SP), BP
//將棧頂向高地址偏移88個字節(jié)從而恢復(fù)parent func stack
57  0x00bf 00191 (<unknown line number>)    ADDQ    $88, SP

58  0x00c3 00195 (<unknown line number>)    RET

//擴容棧
59  0x00c4 00196 (<unknown line number>)    NOP
60  0x00c4 00196 (run.go:5) PCDATA  $0, $-1
61  0x00c4 00196 (run.go:5) PCDATA  $2, $-1
62  0x00c4 00196 (run.go:5) CALL    runtime.morestack_noctxt(SB)

關(guān)鍵函數(shù):
CALL runtime.makeslice(SB) //創(chuàng)建slice對象
CALL runtime.convTslice(SB) //將interface 轉(zhuǎn)換成slice類型
CALL fmt.Fprintln(SB) //打印
CALL runtime.morestack_noctxt(SB) //擴容棧

9 總結(jié)

slice在實際開發(fā)中會經(jīng)常遇到鲸阻,熟悉它的原理,對于理解和運用slice有很大的幫助缨睡。

參考:
深度解密Go語言之Slice--饒全成 [碼農(nóng)桃花源]
深入解析 Go 中 Slice 底層實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸟悴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奖年,更是在濱河造成了極大的恐慌细诸,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陋守,死亡現(xiàn)場離奇詭異震贵,居然都是意外死亡利赋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門猩系,熙熙樓的掌柜王于貴愁眉苦臉地迎上來媚送,“玉大人,你說我怎么就攤上這事寇甸√临耍” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵拿霉,是天一觀的道長吟秩。 經(jīng)常有香客問我,道長友浸,這世上最難降的妖魔是什么峰尝? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮收恢,結(jié)果婚禮上武学,老公的妹妹穿的比我還像新娘。我一直安慰自己伦意,他們只是感情好火窒,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驮肉,像睡著了一般熏矿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上离钝,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天票编,我揣著相機與錄音,去河邊找鬼卵渴。 笑死慧域,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的浪读。 我是一名探鬼主播昔榴,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碘橘!你這毒婦竟也來了互订?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痘拆,失蹤者是張志新(化名)和其女友劉穎仰禽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡坟瓢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年勇边,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片折联。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡粒褒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诚镰,到底是詐尸還是另有隱情奕坟,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布清笨,位于F島的核電站月杉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抠艾。R本人自食惡果不足惜苛萎,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望检号。 院中可真熱鬧腌歉,春花似錦、人聲如沸齐苛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凹蜂。三九已至馍驯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玛痊,已是汗流浹背汰瘫。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留擂煞,地道東北人混弥。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像颈娜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浙宜,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359