成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從零開始學習Go語言的切片

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

[[240632]]

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

 

數組

任何關于 Go 語言切片的討論都要從另一個數據結構也就是數組array開始。Go 的數組有兩個特性:

  1. 數組的長度是固定的;[5]int 是由 5 個 int 構成的數組,和 [3]int 不同。
  2. 數組是值類型。看下面這個示例:

    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 定義了一個類型是 [5]int 的新變量 b,然后把 a 中的內容 復制b 中。改變 ba 中的內容沒有影響,因為 ab 是相互獨立的值。1

 

切片

Go 語言的切片和數組的主要有如下兩個區別:

  1. 切片沒有一個固定的長度。切片的長度不是它類型定義的一部分,而是由切片內部自己維護的。我們可以使用內置的 len 函數知道它的長度。2
  2. 將一個切片賦值給另一個切片時 不會 對切片內容進行復制操作。這是因為切片沒有直接持有其內部數據,而是保留了一個指向 底層數組 3 的指針。數據都保留在底層數組里。

基于第二個特性,兩個切片可以享有共同的底層數組。看下面的示例:

  1. 對切片取切片

    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. }

    在這個例子里,ab 享有共同的底層數組 —— 盡管 b 在數組里的起始偏移量不同,兩者的長度也不同。通過 b 修改底層數組的值也會導致 a 里的值的改變。

  2. 將切片傳進函數

    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. }

    在這個例子里,a 作為形參 s 的實參傳進了 negate 函數,這個函數遍歷 s 內的元素并改變其符號。盡管 nagate 沒有返回值,且沒有訪問到 main 函數里的 a。但是當將之傳進 negate 函數內時,a 里面的值卻被改變了。

大多數程序員都能直觀地了解 Go 語言切片的底層數組是如何工作的,因為它與其它語言中類似數組的工作方式類似。比如下面就是使用 Python 重寫的這一小節的第一個示例:

  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]

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

 

切片頭

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

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

這很重要,因為和 map 以及 chan 這兩個類型不同,切片是值類型,當被賦值或者被作為參數傳入函數時候會被復制過去。

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

  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 對自己的形參 v 的操作沒有影響到 main 中的 v。下面這個示例中的 s 也是 main 中聲明的切片 s 的獨立副本, 而不是 指向 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 的切片是作為值傳遞而不是指針這一點不太尋常。當你在 Go 內定義一個結構體時,90% 的時間里傳遞的都是這個結構體的指針5 。切片的傳遞方式真的很不尋常,我能想到的唯一與之相同的例子只有 time.Time

切片作為值傳遞而不是作為指針傳遞這一特殊行為會讓很多想要理解切片的工作原理的 Go 程序員感到困惑。你只需要記住,當你對切片進行賦值、取切片、傳參或者作為返回值等操作時,你是在復制切片頭結構的三個字段:指向底層數組的指針、長度,以及容量。

 

總結

我們來用引出這一話題的切片作為棧的例子來總結下本文的內容:

  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 函數的最開始我們把一個 nil 切片傳給了函數 f 作為 level 0 。在函數 f 里我們把當前的 level 添加到切片的后面,之后增加 level 的值并進行遞歸。一旦 level 大于 5,函數返回,打印出當前的 level 以及它們復制到的 s 的內容。

  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]

你可以注意到在每一個 levels 的值沒有被別的 f 的調用影響,盡管當計算更高的 level 時作為 append 的副產品,調用棧內的四個 f 函數創建了四個底層數組6 ,但是沒有影響到當前各自的切片。

 

擴展閱讀

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

 

相關文章:

  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. 這不是數組才有的特性,在 Go 語言里中 一切 賦值都是復制過去的。 
  2. 你也可以在對數組使用 len 函數,但是其結果本來就人盡皆知。 
  3. 有時也叫做后臺數組backing array,以及更不嚴謹的說法是后臺切片。 
  4. Go 語言里我們傾向于說值類型以及指針類型,因為 C++ 的引用reference類型這個詞產生誤會。但在這里我認為調用數組作為引用類型是沒有問題的。 
  5. 如果你的結構體有定義在其上的方法或者用于滿足某個接口,那么你傳入結構體指針的比率可以飆升到接近 100%。 
  6. 證明留做習題。  
責任編輯:龐桂玉 來源: Linux中國
相關推薦

2015-05-06 09:36:05

Java語言從零開始學習

2011-04-06 15:55:50

開發webOS程序webOS

2018-04-16 16:31:56

前端開發從零開始

2011-09-05 14:17:54

Sencha ToucMVC

2014-07-22 13:09:21

android

2023-03-21 07:35:43

2015-09-18 10:09:05

Swift

2011-05-24 13:37:16

jQueryAjax

2024-02-23 09:00:00

編程語言編譯器工具

2015-11-17 16:11:07

Code Review

2019-01-18 12:39:45

云計算PaaS公有云

2018-04-18 07:01:59

Docker容器虛擬機

2024-12-06 17:02:26

2020-07-02 15:32:23

Kubernetes容器架構

2015-08-24 14:59:06

Java線程

2018-05-09 20:08:09

人工智能深度學習Python

2024-12-09 09:44:34

機器學習模型分類器

2018-03-14 11:15:06

2011-06-09 10:17:17

jQuery

2010-05-26 17:35:08

配置Xcode SVN
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲视频免费播放 | 日韩二区| 一区二区在线免费观看 | 国产激情偷乱视频一区二区三区 | 国产精品成人一区二区 | 99国内精品久久久久久久 | 欧美日韩中文字幕在线 | 一区在线免费视频 | 欧美二区乱c黑人 | 国产一区2区 | 二区三区av | 国产片网站 | 免费性视频 | 人人草天天草 | 欧美精品一区二区在线观看 | 国产成人精品一区二区 | 国产国产精品久久久久 | 在线观看av网站永久 | 国产精品不卡一区 | 国产精品福利网站 | 天天操天天玩 | 国产日韩欧美在线 | 99久久久久久久久 | 中文字幕在线免费视频 | 在线看h| 国产一区二区三区日韩 | 日韩一区二区三区视频 | 成人精品一区二区三区 | 日韩免费一区二区 | 欧美乱大交xxxxx另类电影 | 亚洲成人一级 | 久久久99精品免费观看 | 亚洲免费精品 | 国产天天操 | h视频网站在线观看 | 在线欧美亚洲 | 精品欧美色视频网站在线观看 | 色接久久 | 国产黄色小视频在线观看 | av天天爽| 毛片一区二区三区 |