切片(slice)是對數組的一個連續片段的引用,所以切片是一個引用類型(因此更類似于c/c++中的數組類型,或python中的list類型),這個片段可以是整個數組,也可以是由起始和終止索引標識的一些項的子集,**需要注意的是,終止索引標識的項不包括在切片內。**
Go語言中切片的內部結構包含地址、大小和容量,切片一般用于快速地操作一塊數據集合。如果將數據集合比作切糕的話,切片就是你要的“那一塊”。切的過程包含從哪里開始(切片的起始位置)及切多大(切片的大小),容量可以理解為裝切片的口袋大小。
## 從數組或切片生成新的切片
切片默認指向一段連續內存區域,可以是數組,也可以是切片本身。
從連續內存區域生成切片是常見的操作,格式如下:
~~~
slice [開始位置 : 結束位置]
~~~
語法說明如下:
* slice:表示目標切片對象;
* 開始位置:對應目標切片對象的索引;
* 結束位置:對應目標切片的結束索引。
從數組或切片生成新的切片擁有如下特性:
* 取出的元素數量為:結束位置 - 開始位置;
* 取出元素不包含結束位置對應的索引,切片最后一個元素使用 slice\[len(slice)\] 獲取;
* 當缺省開始位置時,表示從連續區域開頭到結束位置;
* 當缺省結束位置時,表示從開始位置到整個連續區域末尾;
* 兩者同時缺省時,與切片本身等效;
* 兩者同時為 0 時,等效于空切片,一般用于切片復位。
根據索引位置取切片slice元素值時,取值范圍是(0~len(slice)-1),超界會報運行時錯誤,生成切片時,結束位置可以填寫len(slice),但不會報錯。
#### 1) 從指定范圍中生成切片
切片和數組密不可分,如果將數組理解為一棟辦公樓,那么切片就是把不同的連續樓層出租給使用者,出租的過程需要選擇開始樓層和結束樓層,這個過程就會生成切片,示例代碼如下:
```
var highRiseBuilding [30]int
for i := 0; i < 30; i++ {
highRiseBuilding[i] = i + 1
}
// 區間
fmt.Println(highRiseBuilding[10:15])
// 中間到尾部的所有元素
fmt.Println(highRiseBuilding[20:])
// 開頭到中間指定位置的所有元素
fmt.Println(highRiseBuild[:2])
```
#### 2) 表示原有的切片
生成切片的格式中,當開始和結束位置都被忽略時,生成的切片將表示和原切片一致的切片,并且生成的切片與原切片在數據內容上也是一致的,代碼如下:
```
a := []int{1, 2, 3}
fmt.Println(a[:])
```
#### 3) 重置切片,清空擁有的元素
把切片的開始和結束位置都設為 0 時,生成的切片將變空,代碼如下:
~~~
a := []int{1, 2, 3}
fmt.Println(a[0:0])
~~~
## 直接聲明新的切片
除了可以從原有的數組或者切片中生成切片外,也可以聲明一個新的切片,每一種類型都可以擁有其切片類型,表示多個相同類型元素的連續集合,因此切片類型也可以被聲明,切片類型聲明格式如下:
~~~
var name []Type
~~~
其中 name 表示切片的變量名,Type 表示切片對應的元素類型。
下面代碼展示了切片聲明的使用過程:
~~~
// 聲明字符串切片
var strList []string
// 聲明整型切片
var numList []int
// 聲明一個空切片
var numListEmpty = []int{}
// 輸出3個切片
fmt.Println(strList, numList, numListEmpty)
// 輸出3個切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的結果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
~~~
##使用make()函數構造切片
若是需要動態地創建一個切片,可以使用make()內建函數,格式如下:
```
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
```
其中a和b均是分配2個元素的切片,只是b的內部存儲空間已經分配了10個,但實際使用了2個元素。
容量不會影響當前的元素個數,因此a和b取len都是2。
溫馨提示:
使用make()函數生成的切片一定發生了內存分配操作,但給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的內存區域,設定開始與結束位置,不會發生內存分配操作。
## append()為切片添加元素
Go語言的內奸函數append()可以為切片動態添加元素,格式如下:
```
var a []int
a = append(a, 1) // 追加1個元素
a = append(a, 1, 2, 3) // 追加多個元素,手寫解包方式
a = append(a, []int{1, 2, 3}...) // 追加一個切片,切片需要解包
```
需要注意的是,在使用append()函數為切片動態添加元素時,若空間不足以容納足夠多的元素,切片就會進行“擴容”,此時新切片的長度會發生改變。
切片在擴容時,容量的擴展規律是按容量的2倍數進行擴充。
```
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Println("len: %d, cap: %d, pointer: %p\n", len(numbers), cap(numbers), numbers)
}
```
除了在切片的尾部追加,還可以在切片的開頭添加元素:
```
var a = []int{1, 2, 3}
a = append([]int{0}, a...) // 在開頭添加1個元素
a = append([]int{-3, -2, -1}, a...)在開頭添加一個切片
```
在切片開頭添加元素一般都會導致內存的重新分配,而且會導致已有元素全部被復制1次。因此從切片的開頭添加元素的性能要比從尾部追加元素的性能差很多。
因為append函數返回新切片的特性,所以切片也支持鏈式操作。可以將多個append操作組合起來,實現在切片中間插入元素:
```
var a[]int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i個位置插入x
a = append(a[:i], append([]int{1, 2, 3}, a[i:]...)...)
```
##Go語言切片復制
Go語言的內置函數copy()可以將一個數組切片復制到另外一個數組切片中,若加入的兩個數組切片不一樣大,就會按照其中較小的哪個數組切片的元素個數進行復制。格式如下:
```
copy(destClise, srcSlice []T) slice
```
其中srcSlice為數據來源切片,destSlice為復制的目標(即將srcSlice復制到destSlice),目標切片必須分配過空間且足夠承載復制的元素個數,并且來源和目標的類型必須一致,copy()函數的返回值表示實際發生復制的元素個數。
以下展示了使用copy()函數將一個切片復制到另一個切片的過程:
```
slice1 := []int{1, 2, 3, 4, 5}
slice2 :=[]int{5, 4, 3}
copy(slice2, slice1) // 只會復制slice1中的前3個元素到slice2中
copy(slice1, slice2) // 只會復制slice2中的前3個元素到slice1中
```
雖然通過循環復制切片元素更直接,不過內置的copy()函數使用起來更加方便,copy()函數的第一個參數是要復制的目標slice,第二個參數是源slice,兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。
```
pacage main
import "fmt"
func main() {
// 設置元素數量為1000
const elementCount = 1000
// 預分配足夠多的元素切片
srcData := make([]int, elementCount)
// 將切片賦值
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
// 引用切片數據
refData :=srcData
// 預分配足夠多的元素切片
copyData := make([]int, elementCount)
// 將數據復制到新的切片空間中
copy(copyData, srcData)
// 修改原始數據的第一個元素
srcData[0] = 99
// 打印引用切片的第一個元素
fmt.Println(refData[0])
// 打印復制切片的第一個和最后一個元素
fmt.Println(copyData[0], copyData(elementCount - 1))
// 復制原始數據從4到6(不包含)
copy(copyData, srcData[4:6])
for i := 0; i < 5; i++ {
fmt.Printf("%d", copyData[i])
}
}
```
## Go語言從切片中刪除元素
Go語言并沒有對刪除切片提專用的語法或接口,需要使用切片本身的特性來刪除元素,根據要刪除的元素的位置有三種情況,分別是從開頭位置刪除,從中間位置刪除和從尾部刪除,其中刪除切片尾部的元素速度最快。
### 從開頭位置刪除
刪除開頭的元素可以直接移動數據指針:
```
a := []int{1, 2, 3}
a = a[1:] // 刪除開頭1個元素
a = [N:] // 刪除開頭N個元素
```
也可以不移動數據指針,但是將后面的數據向開頭移動,可以用append原地完成(所謂原地完成是指在原有的切片數據對應的內存區間完成,不會導致內存空間結構的變化):
```
a := []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 刪除開頭1個元素
a = append(a[:0], a[N:]...) // 刪除開頭N個元素
```
還可以使用copy()函數來刪除開頭的元素:
```
a := []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 刪除開頭1個元素
a = a[:copy(a, a[N:])] // 刪除開頭N個元素
```
###從中間位置刪除
對于刪除中間的元素,需要對剩余的元素進行一次整體挪動,同樣可以用append或copy原地完成:
```
a := []int{1, 2, 3}
a = append(a[:i], a[i+1:]...) // 刪除中間1個元素
a = append(a[:i], a[i+N:]...) // 刪除中間N個元素
a = a[:i+copy(a[i:], a[i+1:])] // 刪除中間1個元素
a = a[:i+copy(a[i:], a[i+N:])] // 刪除中間N個元素
```
###從尾部刪除
```
a := []int{1, 2, 3}
a = a[:len(a) - 1] // 刪除尾部1個元素
a = a[:len(a) - N] // 刪除尾部N個元素
```
Go語言中刪除切片元素的本質是,以被刪除元素為分界點,將前后兩個部分的內存重新連接起來。
提示:連續容器的元素刪除無論在任何語言中,都要將刪除點前后的元素移動到新的位置,隨著元素的增加,這個過程將會變得極為耗時,因此,當業務需要大量、頻繁地從一個切片中刪除元素,如果對性能要求較高的話,就需要考慮更換其他的容器了(如雙鏈表等能快速從刪除點刪除元素)。
- 1.Go語言前景
- 2.Go語言環境搭建
- 3.Go語言的基本語法
- 3.1變量
- 3.1.1變量聲明
- 3.1.2變量初始化
- 3.1.3多個變量同時賦值
- 3.1.4匿名變量
- 3.1.5變量的作用域
- 3.1.6整型
- 3.1.7浮點類型
- 3.1.8復數
- 3.1.9bool類型
- 3.1.10字符串
- 3.1.11字符類型
- 3.1.12類型轉換
- 3.2常量
- 3.1.1const關鍵字
- 3.2.2模擬枚舉
- 4.Go語言的流程控制
- 4.2循環結構
- 4.3鍵值循環
- 4.4switch語句
- 4.5goto語句
- 4.6break語句
- 4.7continue語句
- 5.Go語言的函數
- 5.1函數聲明
- 5.2函數變量
- 5.3函數類型實現接口
- 5.4閉包
- 5.5可變參數
- 5.6defer(延遲執行語句)
- 5.7處理運行時錯誤
- 5.8宕機(panic)
- 5.9宕機恢復(recover)
- 5.10Test功能測試函數
- 6.Go語言的內置容器
- 6.1數組
- 6.2切片
- 6.3map
- 6.4sync.Map
- 6.5list
- 6.6range
- 7.Go語言的結構體
- 8.Go語言的接口
- 9.Go語言的常用內置包
- 10.Go語言的并發
- 11.Go語言的文件I/O操作
- 12.Go語言的網絡編程
- 13.Go語言的反射
- 14.Go語言的數據庫編程
- 15.Go語言密碼學算法
- 16.Go語言的gin框架
- 17.Go語言的網絡爬蟲
- 18.Go語言的編譯和工具鏈