切片(slice)(動態分配大小的連續空間)
Go 語言切片的內部結構包含地址、大小和容量。切片一般用於快速地操作一塊數據集合。
1.1、從數組或切片生成新的切片
切片默認指向一段連續內存區域,可以是數組,也可以是切片本身。
從連續內存區域生成切片是常見的操作。格式如下:
slice [開始位置:結束位置] //slice 表示目標切片對象。 //開始位置對應目標切片對象的索引。 //結束位置對應目標切片的結束索引。
從數組生成切片例子:
package main
import (
"fmt"
)
func main() {
var a = [3]int{1, 2, 3} //一個變量a的數組,數組大小是3,已初始化數值1,2,3.
fmt.Println(a, a[1:2]) //使用 a[1:2]生成一個新切片
}

從數組或切片生成新的切片擁有如下特性:a :=[]int[1,2,3,4,5,6,7,8,9,10]
- 取出的元素數量為:結束位置 - 開始位置
- 取出元素不包含結束位置對應的索引,切片最后一個元素使用 slice[len(slice)] 獲取。
- 當缺省開始位置時,表示從連續區域開頭到結束位置,比如 a[:10]。
- 當缺省結束位置時,表示從開始位置到整個連續區域末尾,比如 a[1:]。
- 兩者時同時缺省時,與切片本身等效,比如:a[:]。
- 兩者同時為 0 時,等效於空切片,一般用於切片復位,比如 a[0:0]
- 根據索引位置取切片 slice 元素值時,取值范圍是(0~len(slice)-1),超界會報運行時錯誤。生成切片時,結束位置可以填寫 len(slice) 但不會報錯。
1.2、聲明切片
每一種類型都可以擁有其切片類型,表示多個類型元素的連續集合。因此切片類型也可以被聲明。
切片類型聲明格式如下:
var name []T //name 表示切片類型的變量名 //T 表示切片類型聲明的使用過程
1.3、使用 make() 函數構造切片
如果需要動態地創建一個切片,可以使用 make() 內建函數,格式如下:
make( []T, size, cap) // T: 切片的元素類型。 // size: 就是為這個類型分配多少個月元素。 // cap: 預分配的元素數量,這個值設定后不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。
注意:
- 使用 make() 函數生成的切片一定是發生了內存分配操作。
- 給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的內存區域,設定開始與結束位置,不會發生內存分配操作。
- 切片不一定就必須經過 make() 函數才能使用。
- 生成的切片,聲明后使用 append() 函數均可以正常使用切片。
1.4、使用 append() 函數為切片添加元素
Go 語言的內建函數 append() 可以為切片動態添加元素。每個切片會指向一個內存空間,這片空間能容納一定數量的元素。當空間不能再容納足夠多的元素時,切片就會進行 "擴容"。"擴容" 操作往往發生在 append() 函數調用時。
切片在擴容時,容量的擴展規律按容量的 2 倍數擴充,例如 1、2、4、8、16……,代碼如下:
package main
import "fmt"
func main() {
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d\t cap:%d pointer: %p\n", len(numbers), cap(numbers), numbers)
}
}

append() 函數除了添加一個元素外,也可以一次性添加很多元素。
package main
import "fmt"
func main() {
var car []string
// 添加 1 個元素
car = append(car, "熱烈歡迎世界各地") //使用 append() 函數向切片中添加 1 個元素
fmt.Println("添加 1 個元素:", car)
// 添加 2 個元素
car = append(car, "參加", "2022北京冬奧會", "的代表團!") //使用 append() 函數向切片中添加 多 個元素
fmt.Println("添加 2 個元素:", car)
// 添加切片
team := []string{"Warmly welcome the delegations from all over the world to the 2022 Beijing Winter Olympic Games!"}
car = append(car, team...) //在 team 后面加上了 "...",表示將 team 整個添加到 car 的后面。
fmt.Println(car)
}

1.5、復制切片元素到另一個切片
使用 Go 語言內建的 copy() 函數,可以迅速地將一個切片的數據復制到另外一個切片空間中,copy() 函數的使用格式如下:
copy(destSlice, srcSlice []T) int //srcSlice 為數據來源切片; //destSlice 為復制目標(來源的切片復制給誰)。目標切片必須分配過空間且足夠承載復制的元素個數。來源和目標的類型一致,copy 的返回值表示實際發生復制的元素個數。
下面的代碼將演示對切片的引用和復制操作后對切片元素的影響:
package main
import "fmt"
func main() {
//設置元素數量為 1000
const elementCount = 1000
//預分配足夠多的元素切片
srcData := make([]int, elementCount)
//將切片賦值
for i := 0; i < elementCount; i++ {
srcData[i] = i //srcData 切片中存儲 1000 個元素,元素值分別是連續的 1-1000 數字
}
fmt.Printf("srData 切片的值: %d\n", srcData)
//引用切片數據
//定義一個變量,並將 srcData 值傳遞給它。切片賦值,實際上只是復制了地址,refData 引用了 srcData的地址,因此 srcData的任何修改,都會影響 refData
refData := srcData
fmt.Printf("refData 的內存地址: %p\nsrcData 的內存地址: %p\n", &refData, &srcData)
//預分配足夠多的元素切片
copyData := make([]int, elementCount)
fmt.Printf("copyData 的內存地址: %p\n", ©Data)
//將數據復制到新的切片空間中
copy(copyData, srcData)
fmt.Printf("復制 srcData 后的 copyData 的內存地址: %p\n", ©Data)
//修改原始數據的第一個元素
srcData[0] = 999
fmt.Printf("修改 srcData 第 1 個元素后的 copyData 的內存地址: %p\n", ©Data)
//打印引用切片的一個元素
fmt.Printf("打印引用切片( refData )的一個元素: %d 內存地址: %p\n", refData[0], &refData)
//打印復制切片的第一個和最后一個元素
fmt.Printf("打印復制切片( copyData )的一個元素: %d 內存地址: %p\n", copyData[0], ©Data)
//復制原始數據從 4 到 6(不包含)
for i := 0; i < 5; i++ {
fmt.Printf("%d", copyData[i])
}
fmt.Println(srcData[4:6])
fmt.Println(srcData)
copy(copyData, srcData[4:6])
for i := 0; i < 5; i++ {
fmt.Printf("%d", copyData[i])
}
}
代碼輸出如下:

1.6、從切片中刪除元素
Go 語言中並沒有對刪除切片元素提供專用的語法或者接口,需要使用切片本身的特性來刪除元素。
package main
import "fmt"
func main() {
seq := []string{"a", "b", "c", "d", "e"}
//指定刪除位置
index := 2
//查看刪除位置之間的元素和之后的元素
fmt.Println(seq[:index], seq[index+1:])
// 將刪除點前后的元素連接起來
seq = append(seq[:index], seq[index+1:]...) //后面這3個點要加上,不然會報錯,用於兩個切片合並的。
fmt.Println(seq)
}

