new和make的區別 && 數組和切片

new和make的區別 && 數組和切片html

make、new操做

make用於內建類型(map、slice 和channel)的內存分配。new用於各類類型的內存分配。git

內建函數new本質上說跟其它語言中的同名函數功能同樣:new(T)分配了零值填充的T類型的內存空間,而且返回其地址,即一個*T類型的值。用Go的術語說,它返回了一個指針,指向新分配的類型T的零值。有一點很是重要:數組

new返回指針。數據結構

內建函數make(T, args)與new(T)有着不一樣的功能,make只能建立slice、map和channel,而且返回一個有初始值(非零)的T類型,而不是*T。本質來說,致使這三個類型有所不一樣的緣由是指向數據結構的引用在使用前必須被初始化。例如,一個slice,是一個包含指向數據(內部array)的指針、長度和容量的三項描述符;在這些項目被初始化以前,slice爲nil。對於slice、map和channel來講,make初始化了內部的數據結構,填充適當的值。app

make返回初始化後的(非零)值。函數

下面這個圖詳細的解釋了new和make之間的區別。spa

make和new對應底層的內存分配指針

 

Go數組

Go的切片是在數組之上的抽象數據類型,所以在瞭解切片以前必需要先理解數組。code

數組類型定義了長度和元素類型。例如, [4]int 類型表示一個四個整數的數組。 數組的長度是固定的,長度是數組類型的一部分( [4]int 和 [5]int 是徹底不一樣的類型)。 數組能夠以常規的索引方式訪問,表達式 s[n] 訪問數組的第 n 個元素。regexp

var a [4]int
a[0] = 1
i := a[0]
// i == 1

數組不須要顯式的初始化;數組的零值是能夠直接使用的,數組元素會自動初始化爲其對應類型的零值:

// a[2] == 0, int 類型的零值

類型 [4]int 對應內存中四個連續的整數:

Go的數組是值語義。一個數組變量表示整個數組,它不是指向第一個元素的指針(不像 C 語言的數組)。 當一個數組變量被賦值或者被傳遞的時候,實際上會複製整個數組。 (爲了不復制數組,你能夠傳遞一個指向數組的指針,可是數組指針並非數組。) 能夠將數組看做一個特殊的struct,結構的字段名對應數組的索引,同時成員的數目固定。

數組的字面值像這樣:

b := [2]string{"Penn", "Teller"}

固然,也可讓編譯器統計數組字面值中元素的數目:

b := [...]string{"Penn", "Teller"}

這兩種寫法, b 都是對應 [2]string 類型。

 

Go切片

數組雖然有適用它們的地方,可是數組不夠靈活,所以在Go代碼中數組使用的並很少。 可是,切片則使用得至關普遍。切片基於數組構建,可是提供更強的功能和便利。

切片類型的寫法是 []T , T 是切片元素的類型。和數組不一樣的是,切片類型並無給定固定的長度。

切片的字面值和數組字面值很像,不過切片沒有指定元素個數:

letters := []string{"a", "b", "c", "d"}

切片可使用內置函數 make 建立,函數簽名爲:

func make([]T, len, cap) []T

其中T表明被建立的切片元素的類型。函數 make 接受一個類型、一個長度和一個可選的容量參數。 調用 make 時,內部會分配一個數組,而後返回數組對應的切片。

var s []byte

s = make([]byte, 5, 5)

// s == []byte{0, 0, 0, 0, 0}

當容量參數被忽略時,它默認爲指定的長度。下面是簡潔的寫法:

s := make([]byte, 5)

可使用內置函數 len 和 cap 獲取切片的長度和容量信息。

len(s) == 5

cap(s) == 5

接下來的兩個小節將討論長度和容量之間的關係。

切片的零值爲 nil 。對於切片的零值, len 和 cap 都將返回0。

切片也能夠基於現有的切片或數組生成。切分的範圍由兩個由冒號分割的索引對應的半開區間指定。 例如,表達式 b[1:4] 建立的切片引用數組 b 的第1到3個元素空間(對應切片的索引爲0到2)。

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}

// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

切片的開始和結束的索引都是可選的;它們分別默認爲零和數組的長度。

// b[:2] == []byte{'g', 'o'}

// b[2:] == []byte{'l', 'a', 'n', 'g'}

// b[:] == b

下面語法也是基於數組建立一個切片:

x := [3]string{"Лайка", "Белка", "Стрелка"}

s := x[:] // a slice referencing the storage of x

 

切片的本質

一個切片是一個數組片斷的描述。它包含了指向數組的指針,片斷的長度, 和容量(片斷的最大長度)。

前面使用 make([]byte, 5) 建立的切片變量 s 的結構以下:

長度是切片引用的元素數目。容量是底層數組的元素數目(從切片指針開始)。 關於長度和容量和區域將在下一個例子說明。

咱們繼續對 s 進行切片,觀察切片的數據結構和它引用的底層數組:

s = s[2:4]

切片操做並不複製切片指向的元素。它建立一個新的切片並複用原來切片的底層數組。 這使得切片操做和數組索引同樣高效。所以,經過一個新切片修改元素會影響到原始切片的對應元素。

d := []byte{'r', 'o', 'a', 'd'}

e := d[2:]

// e == []byte{'a', 'd'}

e[1] = 'm'

// e == []byte{'a', 'm'}

// d == []byte{'r', 'o', 'a', 'm'}

前面建立的切片 s 長度小於它的容量。咱們能夠增加切片的長度爲它的容量:

s = s[:cap(s)]

切片增加不能超出其容量。增加超出切片容量將會致使運行時異常,就像切片或數組的索引超 出範圍引發異常同樣。一樣,不能使用小於零的索引去訪問切片以前的元素。

 

切片的生長(copy and append 函數)

要增長切片的容量必須建立一個新的、更大容量的切片,而後將原有切片的內容複製到新的切片。 整個技術是一些支持動態數組語言的常見實現。下面的例子將切片 s 容量翻倍,先建立一個2倍 容量的新切片 t ,複製 s 的元素到 t ,而後將 t 賦值給 s :

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
        t[i] = s[i]
}
s = t

循環中複製的操做能夠由 copy 內置函數替代。copy 函數將源切片的元素複製到目的切片。 它返回複製元素的數目。

func copy(dst, src []T) int

copy 函數支持不一樣長度的切片之間的複製(它只複製較短切片的長度個元素)。 此外, copy 函數能夠正確處理源和目的切片有重疊的狀況。

使用 copy 函數,咱們能夠簡化上面的代碼片斷:

t := make([]byte, len(s), (cap(s)+1)*2)

copy(t, s)

s = t

一個常見的操做是將數據追加到切片的尾部。下面的函數將元素追加到切片尾部, 必要的話會增長切片的容量,最後返回更新的切片:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

下面是 AppendByte 的一種用法:

p := []byte{2, 3, 5}

p = AppendByte(p, 7, 11, 13)

// p == []byte{2, 3, 5, 7, 11, 13}

相似 AppendByte 的函數比較實用,由於它提供了切片容量增加的徹底控制。 根據程序的特色,可能但願分配較小的活較大的塊,或則是超過某個大小再分配。

但大多數程序不須要徹底的控制,所以Go提供了一個內置函數 append , 用於大多數場合;它的函數簽名:

func append(s []T, x ...T) []T

append 函數將 x 追加到切片 s 的末尾,而且在必要的時候增長容量。

a := make([]int, 1)

// a == []int{0}

a = append(a, 1, 2, 3)

// a == []int{0, 1, 2, 3}

若是是要將一個切片追加到另外一個切片尾部,須要使用 ... 語法將第2個參數展開爲參數列表。

因爲切片的零值 nil 用起來就像一個長度爲零的切片,咱們能夠聲明一個切片變量而後在循環 中向它追加數據:

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

 

可能的「陷阱」

正如前面所說,切片操做並不會複製底層的數組。整個數組將被保存在內存中,直到它再也不被引用。 有時候可能會由於一個小的內存引用致使保存全部的數據。

例如, FindDigits 函數加載整個文件到內存,而後搜索第一個連續的數字,最後結果以切片方式返回。

var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

這段代碼的行爲和描述相似,返回的 []byte 指向保存整個文件的數組。由於切片引用了原始的數組, 致使 GC 不能釋放數組的空間;只用到少數幾個字節卻致使整個文件的內容都一直保存在內存裏。

要修復整個問題,能夠將感興趣的數據複製到一個新的切片中:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}

轉載:

https://blog.go-zh.org/go-slices-usage-and-internals

http://www.cnblogs.com/junneyang/p/6070238.html

==========END==========

相關文章
相關標籤/搜索