切片對陣列進行包裝,為資料序列提供了更通用、更強大和更方便的介面,Go中的大多數陣列編程都是用切片而不是簡單的陣列來完成的
slice(切片)代表變長的序列,序列中每個元素都有相同的型別,一個slice型別一般寫作[]T,其中T代表slice中元素的型別,
1.slice創建
- 直接通過初始化引數創建
slice0 := []int{1,3,7,5,2,3,4}
fmt.Println(slice0,len(slice0),cap(slice0))
// [1 3 7 5 2 3 4] 7 7
- 使用內置函式make創建,使用默認值進行初始化
slice1 := make([]int,5,10)
fmt.Println(slice1,len(slice1),cap(slice1))
// [0 0 0 0 0] 5 10
- 從指定下標創建,沒指定的將使用默認值進行初始化,比如int型別是0值,string型別是空串,
slice2 := [...]int{1:1,12:12}
fmt.Println(slice2)
// [0 1 0 0 0 0 0 0 0 0 0 0 12]
fmt.Println(slice2[0],len(slice2),cap(slice2))
// 0 13 13
2.slice的操作
- 下標索引操作
slice3 := []int{1,3,7,5,2,3,4}
fmt.Println(slice3[1])
// 3
fmt.Println(slice3[10])
// 運行時報panic: runtime error: index out of range [10] with length 7
- for回圈操作
slice3 := []int{1,3,7,5,2,3,4}
for _,val := range slice3{
fmt.Println(val)
}
- 切片操作,跟python里面切片一樣
slice3 := []int{1,3,7,5,2,3,4}
fmt.Println(slice3[2:4])
// [7 5]
- 兩個slice不能直接使用 == 進行比較,slice唯一合法的比較操作是和nil比較
slice4 := []int{1,3,7,5,2,3,4}
slice5 := slice4[2:4]
fmt.Println(slice4 == slice5) // 提示:Invalid operation: slice4 == slice5 (operator == is not defined on []int)
fmt.Println(slice4 != nil) // true
- 內置函式的len和cap函式分別回傳slice的長度和容量
- 內置函式append進行添加操作
slice6 := []int{1,3,7,5,2,3,4}
slice7 := append(slice6,0)
slice8 := append(slice7,9,8)
fmt.Println(slice7)
// [1 3 7 5 2 3 4 0]
fmt.Println(slice8)
// [1 3 7 5 2 3 4 0 9 8]
fmt.Println(append(slice6,slice7...)) // 兩個slice進行添加
// [1 3 7 5 2 3 4 1 3 7 5 2 3 4 0]
- 內置函式copy進行拷貝操作
slice10 := []int{1,3,7,5,2,3,4}
var slice9 = make([]int,len(slice10))
fmt.Println(slice9,len(slice9),cap(slice9))
// [0 0 0 0 0 0 0] 7 7
copy(slice9,slice10)
fmt.Println(slice9,len(slice9),cap(slice9))
// [1 3 7 5 2 3 4] 7 7
3.slice的使用注意點
- 切片保存對底層陣列的參考,如果將一個切片分配給另一個切片,則兩個切片都參考同一個陣列, 修改切片的值,會同時影響到源切片的資料
slice11 := []int{1,3,7,5,2,3,4}
slice12 := slice11[2:5]
slice12[1] = 10
fmt.Println(slice11)
// [1 3 7 10 2 3 4]
fmt.Println(slice12)
// [7 10 2]
- 如果函式接受slice引數,則呼叫者可以看到它對slice元素所做的更改,類似于傳遞指向底層陣列的指標,
slice13 := []int{1,3,7,5,2,3,4}
reverse(slice13)
fmt.Println(slice13)
// [4 3 2 5 7 3 1]
func reverse(input_slice []int){
slice_len := len(input_slice)
for i := 0 ; i < slice_len / 2; i++ {
input_slice[i], input_slice[slice_len - i - 1] = input_slice[slice_len - i -1], input_slice[i]
}
}
- slice沒有提供直接洗掉的切片某個元素的內置函式,可以自己組合切片來實作
slice13 := []int{1,3,7,5,2,3,4}
slice14 := append(slice13[:2],slice13[4:len(slice13)]...)
fmt.Println(slice14)
// [4 3 7 3 1] 洗掉下標為3的元素
- 在邊界處拷貝 Slices 和 Maps(uber_go_guide)
接收 Slices的時候注意拷貝,當 map 或 slice 作為函式引數傳入時,如果記憶體儲了對它們的參考,則用戶可以對其進行修改,
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
trips := ...
d1.SetTrips(trips)
// 這里我們修改 trips[0],但不會影響到 d1.trips
trips[0] = ...
回傳 slices的時候注意拷貝,同樣,請注意用戶對暴露內部狀態的 map 或 slice 的修改,
type Stats struct {
mu sync.Mutex
counters map[string]int
}
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
// snapshot 現在是一個拷貝
snapshot := stats.Snapshot()
4.slice的runtime的部分實作
// slice.go
package runtime
// slice的底層的實作結構
type slice struct {
array unsafe.Pointer // 一個陣列的指標
len int
cap int
}
...
// slice的創建
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap)) // uintptr的type的大小 * cap大小的uint指標型別
if overflow || mem > maxAlloc || len < 0 || len > cap {
// 計算有沒有溢位
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)// 使用mallockgc進行記憶體分配, 并回傳unsafe.Pointer指標,指向陣列
}
// append超過容量的時候,進行容量擴展
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
}
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
if et.size == 0 {
// append不應該創建一個帶有nil指標但有非零len的切片
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 計算容量擴容后新的容量
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap { // 新建的容量大于舊容量的兩倍,直接使用當前的容量
newcap = cap
} else {
if old.len < 1024 { // 如果就長度小于1024,直接使用舊的2倍容量
newcap = doublecap
} else {
// 如果舊的容量小于當前容量,則按25%的幅度回圈進行增長,直到大于當前容量
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
switch {
case et.size == 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if sys.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
} else {
shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
}
lenmem = uintptr(old.len) << shift
newlenmem = uintptr(cap) << shift
capmem = roundupsize(uintptr(newcap) << shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.size)
}
if overflow || capmem > maxAlloc {
panic(errorString("growslice: cap out of range"))
}
// 創建新的slice的陣列
var p unsafe.Pointer
if et.ptrdata == 0 {
p = mallocgc(capmem, nil, false)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled {
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
}
}
// 將舊的陣列復制到新的陣列
memmove(p, old.array, lenmem)
// 回傳新的slice結構
return slice{p, old.len, newcap}
}
// slice的copy操作
func slicecopy(toPtr unsafe.Pointer, toLen int, fmPtr unsafe.Pointer, fmLen int, width uintptr) int {
if fmLen == 0 || toLen == 0 {
return 0
}
n := fmLen
if toLen < n {
n = toLen
}
if width == 0 {
return n
}
if raceenabled {
callerpc := getcallerpc()
pc := funcPC(slicecopy)
racereadrangepc(fmPtr, uintptr(n*int(width)), callerpc, pc)
racewriterangepc(toPtr, uintptr(n*int(width)), callerpc, pc)
}
if msanenabled {
msanread(fmPtr, uintptr(n*int(width)))
msanwrite(toPtr, uintptr(n*int(width)))
}
size := uintptr(n) * width
if size == 1 { // common case worth about 2x to do here
// 長度為1表示只是一個位元組,直接進行賦值
*(*byte)(toPtr) = *(*byte)(fmPtr)
} else {
// 否則使用記憶體記憶體拷貝,將源地址資料拷貝到新的陣列
memmove(toPtr, fmPtr, size)
}
return n
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/253159.html
標籤:區塊鏈
上一篇:深入理解分布式技術 - 從區塊鏈技術看分布式理論的應用
下一篇:常見區塊鏈共識問題
