天天看點

高效GO語言程式設計(7)——切片

歡迎加入GolangRoadmap,一個年輕的GO開發者社群https://www.golangroadmap.com/,目前是邀請制注冊,注冊碼:Gopher-1035-0722,已開放GO内推,GO面試,GO寶典,GO返利等欄目

切片

切片通過對數組進行封裝,為資料序列提供了更通用、強大而友善的接口。 除了矩陣變換這類需要明确次元的情況外,Go中的大部分數組程式設計都是通過切片來完成的。

切片儲存了對底層數組的引用,若你将某個切片賦予另一個切片,它們會引用同一個數組。 若某個函數将一個切片作為參數傳入,則它對該切片元素的修改對調用者而言同樣可見, 這可以了解為傳遞了底層數組的指針。是以,Read 函數可接受一個切片實參 而非一個指針和一個計數;切片的長度決定了可讀取資料的上限。以下為 os 包中 File 類型的 Read 方法簽名:

該方法傳回讀取的位元組數和一個錯誤值(若有的話)。若要從更大的緩沖區 b 中讀取前32個位元組,隻需對其進行切片即可。

這種切片的方法常用且高效。若不談效率,以下片段同樣能讀取該緩沖區的前32個位元組。

var n int
    var err error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.
        n += nbytes
        if nbytes == 0 || e != nil {
            err = e
            break
        }
    }
           

隻要切片不超出底層數組的限制,它的長度就是可變的,隻需将它賦予其自身的切片即可。 切片的容量可通過内建函數 cap 獲得,它将給出該切片可取得的最大長度。 以下是将資料追加到切片的函數。若資料超出其容量,則會重新配置設定該切片。傳回值即為所得的切片。 該函數中所使用的 len 和 cap 在應用于 nil 切片時是合法的,它會傳回 0。

func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // 重新配置設定
        // 為未來的增長,雙重配置設定所需的内容.
        newSlice := make([]byte, (l+len(data))*2)
        // copy函數是預先聲明的,适用于任何切片類型。
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}
           

最終我們必須傳回切片,因為盡管 Append 可修改 slice 的元素,但切片自身(其運作時資料結構包含指針、長度和容量)是通過值傳遞的。

向切片追加東西的想法非常有用,是以有專門的内建函數 append。 要了解該函數的設計,我們還需要一些額外的資訊,我們将稍後再介紹它。

二維切片

Go的數組和切片都是一維的。要建立等價的二維數組或切片,就必須定義一個數組的數組, 或切片的切片,就像這樣:

type Transform [3][3]float64  // 一個 3x3 的數組,其實是包含多個數組的一個數組。
type LinesOfText [][]byte     // 包含多個位元組切片的一個切片。
           

由于切片長度是可變的,是以其内部可能擁有多個不同長度的切片。在我們的 LinesOfText 例子中,這是種常見的情況:每行都有其自己的長度。

text := LinesOfText{
	[]byte("Now is the time"),
	[]byte("for all good gophers"),
	[]byte("to bring some fun to the party."),
}
           

有時必須配置設定一個二維數組,例如在處理像素的掃描行時,這種情況就會發生。 我們有兩種方式來達到這個目的。一種就是獨立地配置設定每一個切片;而另一種就是隻配置設定一個數組, 将各個切片都指向它。采用哪種方式取決于你的應用。若切片會增長或收縮, 就應該通過獨立配置設定來避免覆寫下一行;若不會,用單次配置設定來構造對象會更加高效。 以下是這兩種方法的大概代碼,僅供參考。首先是一次一行的:

// 配置設定底層切片.
picture := make([][]uint8, YSize) // y每一行的大小
//循環周遊每一行
for i := range picture {
	picture[i] = make([]uint8, XSize)
}
           

現在是一次配置設定,對行進行切片:

// 配置設定底層切片
picture := make([][]uint8, YSize) //  每 y 個單元一行。
// 配置設定一個大一些的切片以容納所有的元素
pixels := make([]uint8, XSize*YSize) // 指定類型[]uint8, 即便圖檔是 [][]uint8.
//循環周遊圖檔所有行,從剩餘像素切片的前面對每一行進行切片。
for i := range picture {
	picture[i], pixels = pixels[:XSize], pixels[XSize:]
}