日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從零開始學(xué)習(xí)Go語言的切片

這篇文章受到了我與同事討論使用切片slice作為棧stack的一次聊天的啟發(fā)。后來話題聊到了 Go 語言中的切片是如何工作的。我認(rèn)為這些信息對(duì)別人也有用,所以就把它記錄了下來。

成都網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、成都網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團(tuán)成都定制網(wǎng)站等服務(wù)項(xiàng)目。核心團(tuán)隊(duì)均擁有互聯(lián)網(wǎng)行業(yè)多年經(jīng)驗(yàn),服務(wù)眾多知名企業(yè)客戶;涵蓋的客戶類型包括:混凝土泵車等眾多領(lǐng)域,積累了大量豐富的經(jīng)驗(yàn),同時(shí)也獲得了客戶的一致表揚(yáng)!

數(shù)組

任何關(guān)于 Go 語言切片的討論都要從另一個(gè)數(shù)據(jù)結(jié)構(gòu)也就是數(shù)組array開始。Go 的數(shù)組有兩個(gè)特性:

  1. 數(shù)組的長度是固定的;[5]int 是由 5 個(gè) int 構(gòu)成的數(shù)組,和 [3]int 不同。
  2. 數(shù)組是值類型??聪旅孢@個(gè)示例:

       
       
       
       
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6. var a [5]int
    7. b := a
    8. b[2] = 7
    9. fmt.Println(a, b) // prints [0 0 0 0 0] [0 0 7 0 0]
    10. }

    語句 b := a 定義了一個(gè)類型是 [5]int 的新變量 b,然后把 a 中的內(nèi)容 復(fù)制b 中。改變 b 對(duì) a 中的內(nèi)容沒有影響,因?yàn)?ab 是相互獨(dú)立的值。1

切片

Go 語言的切片和數(shù)組的主要有如下兩個(gè)區(qū)別:

  1. 切片沒有一個(gè)固定的長度。切片的長度不是它類型定義的一部分,而是由切片內(nèi)部自己維護(hù)的。我們可以使用內(nèi)置的 len 函數(shù)知道它的長度。2
  2. 將一個(gè)切片賦值給另一個(gè)切片時(shí) 不會(huì) 對(duì)切片內(nèi)容進(jìn)行復(fù)制操作。這是因?yàn)榍衅瑳]有直接持有其內(nèi)部數(shù)據(jù),而是保留了一個(gè)指向 底層數(shù)組 3 的指針。數(shù)據(jù)都保留在底層數(shù)組里。

基于第二個(gè)特性,兩個(gè)切片可以享有共同的底層數(shù)組??聪旅娴氖纠?/p>

  1. 對(duì)切片取切片

       
       
       
       
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6. var a = []int{1,2,3,4,5}
    7. b := a[2:]
    8. b[0] = 0
    9. fmt.Println(a, b) // prints [1 2 0 4 5] [0 4 5]
    10. }

    在這個(gè)例子里,ab 享有共同的底層數(shù)組 —— 盡管 b 在數(shù)組里的起始偏移量不同,兩者的長度也不同。通過 b 修改底層數(shù)組的值也會(huì)導(dǎo)致 a 里的值的改變。

  2. 將切片傳進(jìn)函數(shù)

       
       
       
       
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func negate(s []int) {
    6. for i := range s {
    7. s[i] = -s[i]
    8. }
    9. }
    10.  
    11. func main() {
    12. var a = []int{1, 2, 3, 4, 5}
    13. negate(a)
    14. fmt.Println(a) // prints [-1 -2 -3 -4 -5]
    15. }

    在這個(gè)例子里,a 作為形參 s 的實(shí)參傳進(jìn)了 negate 函數(shù),這個(gè)函數(shù)遍歷 s 內(nèi)的元素并改變其符號(hào)。盡管 nagate 沒有返回值,且沒有訪問到 main 函數(shù)里的 a。但是當(dāng)將之傳進(jìn) negate 函數(shù)內(nèi)時(shí),a 里面的值卻被改變了。

大多數(shù)程序員都能直觀地了解 Go 語言切片的底層數(shù)組是如何工作的,因?yàn)樗c其它語言中類似數(shù)組的工作方式類似。比如下面就是使用 Python 重寫的這一小節(jié)的第一個(gè)示例:

 
 
 
 
  1. Python 2.7.10 (default, Feb 7 2017, 00:08:15)
  2. [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
  3. Type "help", "copyright", "credits" or "license" for more information.
  4. >>> a = [1,2,3,4,5]
  5. >>> b = a
  6. >>> b[2] = 0
  7. >>> a
  8. [1, 2, 0, 4, 5]

以及使用 Ruby 重寫的版本:

 
 
 
 
  1. irb(main):001:0> a = [1,2,3,4,5]
  2. => [1, 2, 3, 4, 5]
  3. irb(main):002:0> b = a
  4. => [1, 2, 3, 4, 5]
  5. irb(main):003:0> b[2] = 0
  6. => 0
  7. irb(main):004:0> a
  8. => [1, 2, 0, 4, 5]

在大多數(shù)將數(shù)組視為對(duì)象或者是引用類型的語言也是如此。4

切片頭

切片同時(shí)擁有值和指針特性的神奇之處在于理解切片實(shí)際上是一個(gè)結(jié)構(gòu)體struct類型。通常在反射reflect包內(nèi)相應(yīng)部分之后的這個(gè)結(jié)構(gòu)體被稱作切片頭slice header。切片頭的定義大致如下:

 
 
 
 
  1. package runtime
  2.  
  3. type slice struct {
  4. ptr unsafe.Pointer
  5. len int
  6. cap int
  7. }

這很重要,因?yàn)楹?map 以及 chan 這兩個(gè)類型不同,切片是值類型,當(dāng)被賦值或者被作為參數(shù)傳入函數(shù)時(shí)候會(huì)被復(fù)制過去。

程序員們都能理解 square 的形參 vmain 中聲明的 v 的是相互獨(dú)立的。請(qǐng)看下面的例子:

 
 
 
 
  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func square(v int) {
  6. v = v * v
  7. }
  8.  
  9. func main() {
  10. v := 3
  11. square(v)
  12. fmt.Println(v) // prints 3, not 9
  13. }

因此 square 對(duì)自己的形參 v 的操作沒有影響到 main 中的 v。下面這個(gè)示例中的 s 也是 main 中聲明的切片 s 的獨(dú)立副本, 而不是 指向 mains 的指針。

 
 
 
 
  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func double(s []int) {
  6. s = append(s, s...)
  7. }
  8.  
  9. func main() {
  10. s := []int{1, 2, 3}
  11. double(s)
  12. fmt.Println(s, len(s)) // prints [1 2 3] 3
  13. }

Go 的切片是作為值傳遞而不是指針這一點(diǎn)不太尋常。當(dāng)你在 Go 內(nèi)定義一個(gè)結(jié)構(gòu)體時(shí),90% 的時(shí)間里傳遞的都是這個(gè)結(jié)構(gòu)體的指針5 。切片的傳遞方式真的很不尋常,我能想到的唯一與之相同的例子只有 time.Time。

切片作為值傳遞而不是作為指針傳遞這一特殊行為會(huì)讓很多想要理解切片的工作原理的 Go 程序員感到困惑。你只需要記住,當(dāng)你對(duì)切片進(jìn)行賦值、取切片、傳參或者作為返回值等操作時(shí),你是在復(fù)制切片頭結(jié)構(gòu)的三個(gè)字段:指向底層數(shù)組的指針、長度,以及容量。

總結(jié)

我們來用引出這一話題的切片作為棧的例子來總結(jié)下本文的內(nèi)容:

 
 
 
 
  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func f(s []string, level int) {
  6. if level > 5 {
  7. return
  8. }
  9. s = append(s, fmt.Sprint(level))
  10. f(s, level+1)
  11. fmt.Println("level:", level, "slice:", s)
  12. }
  13.  
  14. func main() {
  15. f(nil, 0)
  16. }

main 函數(shù)的最開始我們把一個(gè) nil 切片傳給了函數(shù) f 作為 level 0 。在函數(shù) f 里我們把當(dāng)前的 level 添加到切片的后面,之后增加 level 的值并進(jìn)行遞歸。一旦 level 大于 5,函數(shù)返回,打印出當(dāng)前的 level 以及它們復(fù)制到的 s 的內(nèi)容。

 
 
 
 
  1. level: 5 slice: [0 1 2 3 4 5]
  2. level: 4 slice: [0 1 2 3 4]
  3. level: 3 slice: [0 1 2 3]
  4. level: 2 slice: [0 1 2]
  5. level: 1 slice: [0 1]
  6. level: 0 slice: [0]

你可以注意到在每一個(gè) level 內(nèi) s 的值沒有被別的 f 的調(diào)用影響,盡管當(dāng)計(jì)算更高的 level 時(shí)作為 append 的副產(chǎn)品,調(diào)用棧內(nèi)的四個(gè) f 函數(shù)創(chuàng)建了四個(gè)底層數(shù)組6 ,但是沒有影響到當(dāng)前各自的切片。

擴(kuò)展閱讀

如果你想要了解更多 Go 語言內(nèi)切片運(yùn)行的原理,我建議看看 Go 博客里的這些文章:

  • Go Slices: usage and internals (blog.golang.org)
  • Arrays, slices (and strings): The mechanics of 'append' (blog.golang.org)

相關(guān)文章:

  1. If a map isn't a reference variable, what is it?
  2. What is the zero value, and why is it useful?
  3. The empty struct
  4. Should methods be declared on T or *T

  1. 這不是數(shù)組才有的特性,在 Go 語言里中 一切 賦值都是復(fù)制過去的。 
  2. 你也可以在對(duì)數(shù)組使用 len 函數(shù),但是其結(jié)果本來就人盡皆知。 
  3. 有時(shí)也叫做后臺(tái)數(shù)組backing array,以及更不嚴(yán)謹(jǐn)?shù)恼f法是后臺(tái)切片。 
  4. Go 語言里我們傾向于說值類型以及指針類型,因?yàn)?C++ 的引用reference類型這個(gè)詞產(chǎn)生誤會(huì)。但在這里我認(rèn)為調(diào)用數(shù)組作為引用類型是沒有問題的。 
  5. 如果你的結(jié)構(gòu)體有定義在其上的方法或者用于滿足某個(gè)接口,那么你傳入結(jié)構(gòu)體指針的比率可以飆升到接近 100%。 
  6. 證明留做習(xí)題。  

名稱欄目:從零開始學(xué)習(xí)Go語言的切片
轉(zhuǎn)載注明:http://m.5511xx.com/article/codchee.html