鍍金池/ 教程/ GO/ 7.2 切片
4.7 strings 和 strconv 包
13.6 啟動(dòng)外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯(cuò)誤處理與測(cè)試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個(gè)例子:使用 Sorter 接口排序
?# 11.5 測(cè)試一個(gè)值是否實(shí)現(xiàn)了某個(gè)接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語(yǔ)言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運(yùn)行時(shí)異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實(shí)例
12.8 使用接口的實(shí)際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說(shuō)明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測(cè)試和基準(zhǔn)測(cè)試
6.8 閉包
4.9 指針
13.1 錯(cuò)誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過(guò) Git 打包和安裝
2.7 Go 運(yùn)行時(shí)(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動(dòng)態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識(shí)符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對(duì)調(diào)
6.11 計(jì)算函數(shù)執(zhí)行時(shí)間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測(cè)試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語(yǔ)言進(jìn)行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長(zhǎng)參數(shù)
13.9 用(測(cè)試數(shù)據(jù))表驅(qū)動(dòng)測(cè)試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫(kù)
3.3 調(diào)試器
4.5 基本類型和運(yùn)算符
?# 11.8 第二個(gè)例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計(jì)算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯(cuò)誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項(xiàng)目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測(cè)和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時(shí)間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過(guò)內(nèi)存緩存來(lái)提升性能
9.1 標(biāo)準(zhǔn)庫(kù)概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯(cuò)誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測(cè)試多返回值函數(shù)的錯(cuò)誤
6.7 將函數(shù)作為參數(shù)
8.2 測(cè)試鍵值對(duì)是否存在及刪除元素
3.4 構(gòu)建并運(yùn)行 Go 程序
2.1 平臺(tái)與架構(gòu)
5.3 switch 結(jié)構(gòu)

7.2 切片

7.2.1 概念

切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用(該數(shù)組我們稱之為相關(guān)數(shù)組,通常是匿名的),所以切片是一個(gè)引用類型(因此更類似于 C/C++ 中的數(shù)組類型,或者 Python 中的 list 類型)。這個(gè)片段可以是整個(gè)數(shù)組,或者是由起始和終止索引標(biāo)識(shí)的一些項(xiàng)的子集。需要注意的是,終止索引標(biāo)識(shí)的項(xiàng)不包括在切片內(nèi)。切片提供了一個(gè)相關(guān)數(shù)組的動(dòng)態(tài)窗口。

切片是可索引的,并且可以由 len() 函數(shù)獲取長(zhǎng)度。

給定項(xiàng)的切片索引可能比相關(guān)數(shù)組的相同元素的索引小。和數(shù)組不同的是,切片的長(zhǎng)度可以在運(yùn)行時(shí)修改,最小為 0 最大為相關(guān)數(shù)組的長(zhǎng)度:切片是一個(gè) 長(zhǎng)度可變的數(shù)組。

切片提供了計(jì)算容量的函數(shù) cap() 可以測(cè)量切片最長(zhǎng)可以達(dá)到多少:它等于切片的長(zhǎng)度 + 數(shù)組除切片之外的長(zhǎng)度。如果 s 是一個(gè)切片,cap(s) 就是從 s[0] 到數(shù)組末尾的數(shù)組長(zhǎng)度。切片的長(zhǎng)度永遠(yuǎn)不會(huì)超過(guò)它的容量,所以對(duì)于 切片 s 來(lái)說(shuō)該不等式永遠(yuǎn)成立:0 <= len(s) <= cap(s)。

多個(gè)切片如果表示同一個(gè)數(shù)組的片段,它們可以共享數(shù)據(jù);因此一個(gè)切片和相關(guān)數(shù)組的其他切片是共享存儲(chǔ)的,相反,不同的數(shù)組總是代表不同的存儲(chǔ)。數(shù)組實(shí)際上是切片的構(gòu)建塊。

優(yōu)點(diǎn) 因?yàn)榍衅且茫运鼈儾恍枰褂妙~外的內(nèi)存并且比使用數(shù)組更有效率,所以在 Go 代碼中 切片比數(shù)組更常用。

聲明切片的格式是: var identifier []type(不需要說(shuō)明長(zhǎng)度)。

一個(gè)切片在未初始化之前默認(rèn)為 nil,長(zhǎng)度為 0。

切片的初始化格式是:var slice1 []type = arr1[start:end]。

這表示 slice1 是由數(shù)組 arr1 從 start 索引到 end-1 索引之間的元素構(gòu)成的子集(切分?jǐn)?shù)組,start:end 被稱為 slice 表達(dá)式)。所以 slice1[0] 就等于 arr1[start]。這可以在 arr1 被填充前就定義好。

如果某個(gè)人寫:var slice1 []type = arr1[:] 那么 slice1 就等于完整的 arr1 數(shù)組(所以這種表示方式是 arr1[0:len(arr1)] 的一種縮寫)。另外一種表述方式是:slice1 = &arr1。

arr1[2:]arr1[2:len(arr1)] 相同,都包含了數(shù)組從第三個(gè)到最后的所有元素。

arr1[:3]arr1[0:3] 相同,包含了從第一個(gè)到第三個(gè)元素(不包括第三個(gè))。

如果你想去掉 slice1 的最后一個(gè)元素,只要 slice1 = slice1[:len(slice1)-1]。

一個(gè)由數(shù)字 1、2、3 組成的切片可以這么生成:s := [3]int{1,2,3}[:] 甚至更簡(jiǎn)單的 s := []int{1,2,3}。

s2 := s[:] 是用切片組成的切片,擁有相同的元素,但是仍然指向相同的相關(guān)數(shù)組。

一個(gè)切片 s 可以這樣擴(kuò)展到它的大小上限:s = s[:cap(s)],如果再擴(kuò)大的話就會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤(參見第 7.7 節(jié))。

對(duì)于每一個(gè)切片(包括 string),以下狀態(tài)總是成立的:

s == s[:i] + s[i:] // i是一個(gè)整數(shù)且: 0 <= i <= len(s)
len(s) <= cap(s)

切片也可以用類似數(shù)組的方式初始化:var x = []int{2, 3, 5, 7, 11}。這樣就創(chuàng)建了一個(gè)長(zhǎng)度為 5 的數(shù)組并且創(chuàng)建了一個(gè)相關(guān)切片。

切片在內(nèi)存中的組織方式實(shí)際上是一個(gè)有 3 個(gè)域的結(jié)構(gòu)體:指向相關(guān)數(shù)組的指針,切片長(zhǎng)度以及切片容量。下圖給出了一個(gè)長(zhǎng)度為 2,容量為 4 的切片y。

  • y[0] = 3y[1] = 5。
  • 切片 y[0:4] 由 元素 3,5,7 和 11 組成。

http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.2_fig7.2.png?raw=true" alt="" />

示例 7.7 array_slices.go

package main
import "fmt"

func main() {
    var arr1 [6]int
    var slice1 []int = arr1[2:5] // item at index 5 not included!

    // load the array with integers: 0,1,2,3,4,5
    for i := 0; i < len(arr1); i++ {
        arr1[i] = i
    }

    // print the slice
    for i := 0; i < len(slice1); i++ {
        fmt.Printf("Slice at %d is %d\n", i, slice1[i])
    }

    fmt.Printf("The length of arr1 is %d\n", len(arr1))
    fmt.Printf("The length of slice1 is %d\n", len(slice1))
    fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))

    // grow the slice
    slice1 = slice1[0:4]
    for i := 0; i < len(slice1); i++ {
        fmt.Printf("Slice at %d is %d\n", i, slice1[i])
    }
    fmt.Printf("The length of slice1 is %d\n", len(slice1))
    fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))

    // grow the slice beyond capacity
    //slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range
}

輸出:

Slice at 0 is 2  
Slice at 1 is 3  
Slice at 2 is 4  
The length of arr1 is 6  
The length of slice1 is 3  
The capacity of slice1 is 4  
Slice at 0 is 2  
Slice at 1 is 3  
Slice at 2 is 4  
Slice at 3 is 5  
The length of slice1 is 4  
The capacity of slice1 is 4  

如果 s2 是一個(gè) slice,你可以將 s2 向后移動(dòng)一位 s2 = s2[1:],但是末尾沒(méi)有移動(dòng)。切片只能向后移動(dòng),s2 = s2[-1:] 會(huì)導(dǎo)致編譯錯(cuò)誤。切片不能被重新分片以獲取數(shù)組的前一個(gè)元素。

注意 絕對(duì)不要用指針指向 slice。切片本身已經(jīng)是一個(gè)引用類型,所以它本身就是一個(gè)指針!!

問(wèn)題 7.2: 給定切片 b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'},那么 b[1:4]、b[:2]、b[2:]b[:] 分別是什么?

7.2.2 將切片傳遞給函數(shù)

如果你有一個(gè)函數(shù)需要對(duì)數(shù)組做操作,你可能總是需要把參數(shù)聲明為切片。當(dāng)你調(diào)用該函數(shù)時(shí),把數(shù)組分片,創(chuàng)建為一個(gè) 切片引用并傳遞給該函數(shù)。這里有一個(gè)計(jì)算數(shù)組元素和的方法:

func sum(a []int) int {
    s := 0
    for i := 0; i < len(a); i++ {
        s += a[i]
    }
    return s
}

func main() {
    var arr = [5]int{0, 1, 2, 3, 4}
    sum(arr[:])
}

7.2.3 用 make() 創(chuàng)建一個(gè)切片

當(dāng)相關(guān)數(shù)組還沒(méi)有定義時(shí),我們可以使用 make() 函數(shù)來(lái)創(chuàng)建一個(gè)切片 同時(shí)創(chuàng)建好相關(guān)數(shù)組:var slice1 []type = make([]type, len)。

也可以簡(jiǎn)寫為 slice1 := make([]type, len),這里 len 是數(shù)組的長(zhǎng)度并且也是 slice 的初始長(zhǎng)度。

所以定義 s2 := make([]int, 10),那么 cap(s2) == len(s2) == 10

make 接受 2 個(gè)參數(shù):元素的類型以及切片的元素個(gè)數(shù)。

如果你想創(chuàng)建一個(gè) slice1,它不占用整個(gè)數(shù)組,而只是占用以 len 為個(gè)數(shù)個(gè)項(xiàng),那么只要:slice1 := make([]type, len, cap)

make 的使用方式是:func make([]T, len, cap),其中 cap 是可選參數(shù)。

所以下面兩種方法可以生成相同的切片:

make([]int, 50, 100)
new([100]int)[0:50]

下圖描述了使用 make 方法生成的切片的內(nèi)存結(jié)構(gòu):http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.2_fig7.2.1.png?raw=true" alt="" />

示例 7.8 make_slice.go

package main
import "fmt"

func main() {
    var slice1 []int = make([]int, 10)
    // load the array/slice:
    for i := 0; i < len(slice1); i++ {
        slice1[i] = 5 * i
    }

    // print the slice:
    for i := 0; i < len(slice1); i++ {
        fmt.Printf("Slice at %d is %d\n", i, slice1[i])
    }
    fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
    fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
}

輸出:

Slice at 0 is 0  
Slice at 1 is 5  
Slice at 2 is 10  
Slice at 3 is 15  
Slice at 4 is 20  
Slice at 5 is 25  
Slice at 6 is 30  
Slice at 7 is 35  
Slice at 8 is 40  
Slice at 9 is 45  

The length of slice1 is 10  
The capacity of slice1 is 10  

因?yàn)樽址羌兇獠豢勺兊淖止?jié)數(shù)組,它們也可以被切分成 切片。

練習(xí) 7.4: fobinacci_funcarray.go: 為練習(xí) 7.3 寫一個(gè)新的版本,主函數(shù)調(diào)用一個(gè)使用序列個(gè)數(shù)作為參數(shù)的函數(shù),該函數(shù)返回一個(gè)大小為序列個(gè)數(shù)的 Fibonacci 切片。

7.2.4 new() 和 make() 的區(qū)別

看起來(lái)二者沒(méi)有什么區(qū)別,都在堆上分配內(nèi)存,但是它們的行為不同,適用于不同的類型。

  • new(T) 為每個(gè)新的類型T分配一片內(nèi)存,初始化為 0 并且返回類型為*T的內(nèi)存地址:這種方法 返回一個(gè)指向類型為 T,值為 0 的地址的指針,它適用于值類型如數(shù)組和結(jié)構(gòu)體(參見第 10 章);它相當(dāng)于 &T{}。
  • make(T) 返回一個(gè)類型為 T 的初始值,它只適用于3種內(nèi)建的引用類型:切片、map 和 channel(參見第 8 章,第 13 章)。

換言之,new 函數(shù)分配內(nèi)存,make 函數(shù)初始化;下圖給出了區(qū)別:

http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.3_fig7.3.png?raw=true" alt="" />

在圖 7.3 的第一幅圖中:

var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)

在第二幅圖中, p := make([]int, 0) ,切片 已經(jīng)被初始化,但是指向一個(gè)空的數(shù)組。

以上兩種方式實(shí)用性都不高。下面的方法:

var v []int = make([]int, 10, 50)

或者

v := make([]int, 10, 50)

這樣分配一個(gè)有 50 個(gè) int 值的數(shù)組,并且創(chuàng)建了一個(gè)長(zhǎng)度為 10,容量為 50 的 切片 v,該 切片 指向數(shù)組的前 10 個(gè)元素。

問(wèn)題 7.3 給定 s := make([]byte, 5),len(s) 和 cap(s) 分別是多少?s = s[2:4],len(s) 和 cap(s) 又分別是多少?
問(wèn)題 7.4 假設(shè) s1 := []byte{'p', 'o', 'e', 'm'}s2 := s1[2:],s2 的值是多少?如果我們執(zhí)行 s2[1] = 't',s1 和 s2 現(xiàn)在的值又分別是多少?

7.2.5 多維 切片

和數(shù)組一樣,切片通常也是一維的,但是也可以由一維組合成高維。通過(guò)分片的分片(或者切片的數(shù)組),長(zhǎng)度可以任意動(dòng)態(tài)變化,所以 Go 語(yǔ)言的多維切片可以任意切分。而且,內(nèi)層的切片必須單獨(dú)分配(通過(guò) make 函數(shù))。

7.2.6 bytes 包

類型 []byte 的切片十分常見,Go 語(yǔ)言有一個(gè) bytes 包專門用來(lái)解決這種類型的操作方法。

bytes 包和字符串包十分類似(參見第 4.7 節(jié))。而且它還包含一個(gè)十分有用的類型 Buffer:

import "bytes"

type Buffer struct {
    ...
}

這是一個(gè)長(zhǎng)度可變的 bytes 的 buffer,提供 Read 和 Write 方法,因?yàn)樽x寫長(zhǎng)度未知的 bytes 最好使用 buffer。

Buffer 可以這樣定義:var buffer bytes.Buffer。

或者使用 new 獲得一個(gè)指針:var r *bytes.Buffer = new(bytes.Buffer)。

或者通過(guò)函數(shù):func NewBuffer(buf []byte) *Buffer,創(chuàng)建一個(gè) Buffer 對(duì)象并且用 buf 初始化好;NewBuffer 最好用在從 buf 讀取的時(shí)候使用。

通過(guò) buffer 串聯(lián)字符串

類似于 Java 的 StringBuilder 類。

在下面的代碼段中,我們創(chuàng)建一個(gè) buffer,通過(guò) buffer.WriteString(s) 方法將字符串 s 追加到后面,最后再通過(guò) buffer.String() 方法轉(zhuǎn)換為 string:

var buffer bytes.Buffer
for {
    if s, ok := getNextString(); ok { //method getNextString() not shown here
        buffer.WriteString(s)
    } else {
        break
    }
}
fmt.Print(buffer.String(), "\n")

這種實(shí)現(xiàn)方式比使用 += 要更節(jié)省內(nèi)存和 CPU,尤其是要串聯(lián)的字符串?dāng)?shù)目特別多的時(shí)候。

練習(xí) 7.5 給定切片 sl,將一個(gè) []byte 數(shù)組追加到 sl 后面。寫一個(gè)函數(shù) Append(slice, data []byte) []byte,該函數(shù)在 sl 不能存儲(chǔ)更多數(shù)據(jù)的時(shí)候自動(dòng)擴(kuò)容。
練習(xí) 7.6 把一個(gè)緩存 buf 分片成兩個(gè) 切片:第一個(gè)是前 n 個(gè) bytes,后一個(gè)是剩余的,用一行代碼實(shí)現(xiàn)。

鏈接