[TOC]
## 7.2.1 概念
切片(slice)是對數組一個連續片段的引用(該數組我們稱之為相關數組,通常是匿名的),所以切片是一個引用類型(因此更類似于 C/C++ 中的數組類型,或者 Python 中的 list 類型)。這個片段可以是整個數組,或者是由起始和終止索引標識的一些項的子集。需要注意的是,終止索引標識的項不包括在切片內。切片提供了一個相關數組的動態窗口。
切片是可索引的,并且可以由?`len()`?函數獲取長度。
給定項的切片索引可能比相關數組的相同元素的索引小。和數組不同的是,切片的長度可以在運行時修改,最小為 0 最大為相關數組的長度:切片是一個?**長度可變的數組**。
切片提供了計算容量的函數?`cap()`?可以測量切片最長可以達到多少:它等于切片的長度 + 數組除切片之外的長度。如果 s 是一個切片,`cap(s)`?就是從?`s[0]`?到數組末尾的數組長度。切片的長度永遠不會超過它的容量,所以對于 切片 s 來說該不等式永遠成立:`0 <= len(s) <= cap(s)`。
多個切片如果表示同一個數組的片段,它們可以共享數據;因此一個切片和相關數組的其他切片是共享存儲的,相反,不同的數組總是代表不同的存儲。數組實際上是切片的構建塊。
**優點**?因為切片是引用,所以它們不需要使用額外的內存并且比使用數組更有效率,所以在 Go 代碼中 切片比數組更常用。
聲明切片的格式是:?`var identifier []type`(不需要說明長度)。
一個切片在未初始化之前默認為 nil,長度為 0。
切片的初始化格式是:`var slice1 []type = arr1[start:end]`。
這表示 slice1 是由數組 arr1 從 start 索引到?`end-1`?索引之間的元素構成的子集(切分數組,start:end 被稱為 slice 表達式)。所以?`slice1[0]`?就等于?`arr1[start]`。這可以在 arr1 被填充前就定義好。
如果某個人寫:`var slice1 []type = arr1[:]`?那么 slice1 就等于完整的 arr1 數組(所以這種表示方式是`arr1[0:len(arr1)]`?的一種縮寫)。另外一種表述方式是:`slice1 = &arr1`。
`arr1[2:]`?和?`arr1[2:len(arr1)]`?相同,都包含了數組從第二個到最后的所有元素。
`arr1[:3]`?和?`arr1[0:3]`?相同,包含了從第一個到第三個元素(不包括第三個)。
如果你想去掉 slice1 的最后一個元素,只要?`slice1 = slice1[:len(slice1)-1]`。
一個由數字 1、2、3 組成的切片可以這么生成:`s := [3]int{1,2,3}`?甚至更簡單的?`s := []int{1,2,3}`。
`s2 := s[:]`?是用切片組成的切片,擁有相同的元素,但是仍然指向相同的相關數組。
一個切片 s 可以這樣擴展到它的大小上限:`s = s[:cap(s)]`,如果再擴大的話就會導致運行時錯誤(參見第 7.7 節)。
對于每一個切片(包括 string),以下狀態總是成立的:
~~~
s == s[:i] + s[i:] // i是一個整數且: 0 <= i <= len(s)
len(s) < cap(s)
~~~
切片也可以用類似數組的方式初始化:`var x = []int{2, 3, 5, 7, 11}`。這樣就創建了一個長度為 5 的數組并且創建了一個相關切片。
切片在內存中的組織方式實際上是一個有 3 個域的結構體:指向相關數組的指針,切片 長度以及切片容量。下圖給出了一個長度為 2,容量為 4 的切片。
* `y[0] = 3`?且?`y[1] = 5`。
* 切片?`y[0:4]`?由 元素 3, 5, 7 和 11 組成。
[](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/images/7.2_fig7.2.png?raw=true)
示例 7.7?[array_slices.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_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 是一個 slice,你可以將 s2 向后移動一位?`s2 = s2[1:]`,但是末尾沒有移動。切片只能向后移動,`s2 = s2[-1:]`會導致編譯錯誤。切片不能被重新分片以獲取數組的前一個元素。
**注意**?絕對不要用指針指向 slice。切片本身已經是一個引用類型,所以它本身就是一個指針!!
問題 7.2: 給定切片?`b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}`,那么?`b[1:4]`、`b[:2]`、`b[2:]`?和?`b[:]`?分別是什么?
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md#722-將切片傳遞給函數)7.2.2 將切片傳遞給函數
如果你有一個函數需要對數組做操作,你可能總是需要把參數聲明為切片。當你調用該函數時,把數組分片,創建為一個 切片引用并傳遞給該函數。這里有一個計算數組元素和的方法:
~~~
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[:])
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md#723-用-make-創建一個切片)7.2.3 用 make() 創建一個切片
當相關數組還沒有定義時,我們可以使用 make() 函數來創建一個切片 同時創建好相關數組:`var slice1 []type = make([]type, len)`。
也可以簡寫為?`slice1 := make([]type, len)`,這里?`len`?是數組的長度并且也是?`slice`?的初始長度。
所以定義?`s2 := make([]int, 10)`,那么?`cap(s2) == len(s2) == 10`。
make 接受 2 個參數:元素的類型以及切片的元素個數。
如果你想創建一個 slice1,它不占用整個數組,而只是占用以 len 為個數個項,那么只要:`slice1 := make([]type, len, cap)`。
make 的使用方式是:`func make([]T, len, cap)`,其中 cap 是可選參數。
所以下面兩種方法可以生成相同的切片:
~~~
make([]int, 50, 100)
new([100]int)[0:50]
~~~
下圖描述了使用 make 方法生成的切片的內存結構:[](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/images/7.2_fig7.2.1.png?raw=true)
示例 7.8?[make_slice.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_7/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
~~~
因為字符串是純粹不可變的字節數組,它們也可以被切分成 切片。
練習 7.4: fobinacci_funcarray.go: 為練習 7.3 寫一個新的版本,主函數調用一個使用序列個數作為參數的函數,該函數返回一個大小為序列個數的 Fibonacci 切片。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md#724-new-和-make-的區別)7.2.4 new() 和 make() 的區別
看起來二者沒有什么區別,都在堆上分配內存,但是它們的行為不同,適用于不同的類型。
* new(T) 為每個新的類型T分配一片內存,初始化為 0 并且返回類型為*T的內存地址:這種方法?**返回一個指向類型為 T,值為 0 的地址的指針**,它適用于值類型如數組和結構體(參見第 10 章);它相當于?`&T{}`。
* make(T)?**返回一個類型為 T 的初始值**,它只適用于3種內建的引用類型:切片、map 和 channel(參見第 8 章,第 13 章)。
換言之,new 函數分配內存,make 函數初始化;下圖給出了區別:
[](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/images/7.3_fig7.3.png?raw=true)
在圖 7.3 的第一幅圖中:
~~~
var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)
~~~
在第二幅圖中,?`p := make([]int, 0)`?,切片 已經被初始化,但是指向一個空的數組。
以上兩種方式實用性都不高。下面的方法:
~~~
var v []int = make([]int, 10, 50)
~~~
或者
~~~
v := make([]int, 10, 50)
~~~
這樣分配一個有 50 個 int 值的數組,并且創建了一個長度為 10,容量為 50 的 切片 v,該 切片 指向數組的前 10 個元素。
**問題 7.3**?給定?`s := make([]byte, 5)`,len(s) 和 cap(s) 分別是多少?`s = s[2:4]`,len(s) 和 cap(s) 又分別是多少?
**問題 7.4**?假設?`s1 := []byte{'p', 'o', 'e', 'm'}`?且?`s2 := d[2:]`,s2 的值是多少?如果我們執行?`s2[1] == 't'`,s1 和 s2 現在的值又分配是多少?
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md#725-多維-切片)7.2.5 多維 切片
和數組一樣,切片通常也是一維的,但是也可以由一維組合成高維。通過分片的分片(或者切片的數組),長度可以任意動態變化,所以 Go 語言的多維切片可以任意切分。而且,內層的切片必須單獨分配(通過 make 函數)。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md#726-bytes-包)7.2.6 bytes 包
類型?`[]byte`?的切片十分常見,Go 語言有一個 bytes 包專門用來解決這種類型的操作方法。
bytes 包和字符串包十分類似(參見第 4.7 節)。而且它還包含一個十分有用的類型 Buffer:
~~~
import "bytes"
type Buffer struct {
...
}
~~~
這是一個長度可變的 bytes 的 buffer,提供 Read 和 Write 方法,因為讀寫長度未知的 bytes 最好使用 buffer。
Buffer 可以這樣定義:`var buffer bytes.Buffer`。
或者使用 new 獲得一個指針:`var r *bytes.Buffer = new(bytes.Buffer)`。
或者通過函數:`func NewBuffer(buf []byte) *Buffer`,創建一個 Buffer 對象并且用 buf 初始化好;NewBuffer 最好用在從 buf 讀取的時候使用。
**通過 buffer 串聯字符串**
類似于 Java 的 StringBuilder 類。
在下面的代碼段中,我們創建一個 buffer,通過?`buffer.WriteString(s)`?方法將字符串 s 追加到后面,最后再通過`buffer.String()`?方法轉換為 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")
~~~
這種實現方式比使用?`+=`?要更節省內存和 CPU,尤其是要串聯的字符串數目特別多的時候。
**練習 7.5**?給定切片 sl,將一個?`[]byte`?數組追加到 sl 后面。寫一個函數?`Append(slice, data []byte) []byte`,該函數在 sl 不能存儲更多數據的時候自動擴容。
**練習 7.6**?把一個緩存 buf 分片成兩個 切片:第一個是前 n 個 bytes,后一個是剩余的,用一行代碼實現。
- 前言
- 第一部分:學習 Go 語言
- 第1章:Go 語言的起源,發展與普及
- 1.1 起源與發展
- 1.2 語言的主要特性與發展的環境和影響因素
- 第2章:安裝與運行環境
- 2.1 平臺與架構
- 2.2 Go 環境變量
- 2.3 在 Linux 上安裝 Go
- 2.4 在 Mac OS X 上安裝 Go
- 2.5 在 Windows 上安裝 Go
- 2.6 安裝目錄清單
- 2.7 Go 運行時(runtime)
- 2.8 Go 解釋器
- 第3章:編輯器、集成開發環境與其它工具
- 3.1 Go 開發環境的基本要求
- 3.2 編輯器和集成開發環境
- 3.3 調試器
- 3.4 構建并運行 Go 程序
- 3.5 格式化代碼
- 3.6 生成代碼文檔
- 3.7 其它工具
- 3.8 Go 性能說明
- 3.9 與其它語言進行交互
- 第二部分:語言的核心結構與技術
- 第4章:基本結構和基本數據類型
- 4.1 文件名、關鍵字與標識符
- 4.2 Go 程序的基本結構和要素
- 4.3 常量
- 4.4 變量
- 4.5 基本類型和運算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 時間和日期
- 4.9 指針
- 第5章:控制結構
- 5.1 if-else 結構
- 5.2 測試多返回值函數的錯誤
- 5.3 switch 結構
- 5.4 for 結構
- 5.5 Break 與 continue
- 5.6 標簽與 goto
- 第6章:函數(function)
- 6.1 介紹
- 6.2 函數參數與返回值
- 6.3 傳遞變長參數
- 6.4 defer 和追蹤
- 6.5 內置函數
- 6.6 遞歸函數
- 6.7 將函數作為參數
- 6.8 閉包
- 6.9 應用閉包:將函數作為返回值
- 6.10 使用閉包調試
- 6.11 計算函數執行時間
- 6.12 通過內存緩存來提升性能
- 第7章:數組與切片
- 7.1 聲明和初始化
- 7.2 切片
- 7.3 For-range 結構
- 7.4 切片重組(reslice)
- 7.5 切片的復制與追加
- 7.6 字符串、數組和切片的應用
- 第8章:Map
- 8.1 聲明、初始化和 make
- 8.2 測試鍵值對是否存在及刪除元素
- 8.3 for-range 的配套用法
- 8.4 map 類型的切片
- 8.5 map 的排序
- 8.6 將 map 的鍵值對調
- 第9章:包(package)
- 9.1 標準庫概述
- 9.2 regexp 包
- 9.3 鎖和 sync 包
- 9.4 精密計算和 big 包
- 9.5 自定義包和可見性
- 9.6 為自定義包使用 godoc
- 9.7 使用 go install 安裝自定義包
- 9.8 自定義包的目錄結構、go install 和 go test
- 9.9 通過 Git 打包和安裝
- 9.10 Go 的外部包和項目
- 9.11 在 Go 程序中使用外部庫
- 第10章:結構(struct)與方法(method)
- 10.1 結構體定義
- 10.2 使用工廠方法創建結構體實例
- 10.3 使用自定義包中的結構體
- 10.4 帶標簽的結構體
- 10.5 匿名字段和內嵌結構體
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)與反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 類型斷言:如何檢測和轉換接口變量的類型
- 11.4 類型判斷:type-switch
- 11.5 測試一個值是否實現了某個接口
- 11.6 使用方法集與接口
- 11.7 第一個例子:使用 Sorter 接口排序
- 11.8 第二個例子:讀和寫
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高級編程
- 第12章 讀寫數據
- 12.1 讀取用戶的輸入
- 12.2 文件讀寫
- 12.3 文件拷貝
- 12.4 從命令行讀取參數
- 12.5 用buffer讀取文件
- 12.6 用切片讀寫文件
- 12.7 用 defer 關閉文件
- 12.8 使用接口的實際例子:fmt.Fprintf
- 12.9 Json 數據格式
- 12.10 XML 數據格式
- 12.11 用 Gob 傳輸數據
- 12.12 Go 中的密碼學
- 第13章 錯誤處理與測試
- 13.1 錯誤處理
- 13.2 運行時異常和 panic
- 13.3 從 panic 中恢復(Recover)
- 13.4 自定義包中的錯誤處理和 panicking
- 13.5 一種用閉包處理錯誤的模式
- 13.6 啟動外部命令和程序
- 13.7 Go 中的單元測試和基準測試
- 13.8 測試的具體例子
- 13.9 用(測試數據)表驅動測試
- 13.10 性能調試:分析并優化 Go 程序
- 第14章:協程(goroutine)與通道(channel)
- 14.1 并發、并行和協程
- 14.2 使用通道進行協程間通信
- 14.3 協程同步:關閉通道-對阻塞的通道進行測試
- 14.4 使用 select 切換協程
- 14.5 通道,超時和計時器(Ticker)
- 14.6 協程和恢復(recover)
- 第15章:網絡、模版與網頁應用
- 15.1 tcp服務器
- 15.2 一個簡單的web服務器
- 15.3 訪問并讀取頁面數據
- 15.4 寫一個簡單的網頁應用
- 第四部分:實際應用
- 第16章:常見的陷阱與錯誤
- 16.1 誤用短聲明導致變量覆蓋
- 16.2 誤用字符串
- 16.3 發生錯誤時使用defer關閉一個文件
- 16.5 不需要將一個指向切片的指針傳遞給函數
- 16.6 使用指針指向接口類型
- 16.7 使用值類型時誤用指針
- 16.8 誤用協程和通道
- 16.9 閉包和協程的使用
- 16.10 糟糕的錯誤處理
- 第17章:模式
- 17.1 關于逗號ok模式
- 第18章:出于性能考慮的實用代碼片段
- 18.1 字符串
- 18.2 數組和切片
- 18.3 映射
- 18.4 結構體
- 18.5 接口
- 18.6 函數
- 18.7 文件
- 18.8 協程(goroutine)與通道(channel)
- 18.9 網絡和網頁應用
- 18.10 其他
- 18.11 出于性能考慮的最佳實踐和建議
- 附錄