Go(03) slice切片的使用

原文連接   

http://www.limerence2017.com/2019/05/08/golang05/#more

golang 的引用類型和內置類型變量

golang 中變量類型分爲引用類型和值類型(也叫做內置類型)golang

1.值類型:變量直接存儲值,內存一般在棧中分配。

值類型:基本數據類型int、float、bool、string以及數組和struct數組

2.引用類型:變量存儲的是一個地址,這個地址存儲最終的值。內存一般在 堆上分配。經過GC回收。

引用類型:指針、slice、map、chan等都是引用類型。這類型變量須要經過make構造app

golang中函數傳參只有一種方式

golang中函數傳遞參數,只有值傳遞一種,也就是實參內容按照值copy方式傳遞給形參。
當函數的形參變量類型爲指針,slice,map,chan等類型時,雖然實參和形參地址不一樣,可是內部指向了同一個地址,因此能夠達到修改指定空間數據的目的。
不要着急,接下來我會寫一寫小demo幫助你們理解。
函數

數組

先把這段代碼寫一遍看看結果spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//數組聲明方法
var bytearray [8]byte //長度爲8的數組
fmt.Println(bytearray)
var pointarray [4]*float64 //指針數組
fmt.Println(pointarray)
var mularray [3][5]int
fmt.Println(mularray)
fmt.Printf(" pointarray len is %v\n", len(pointarray))
//數組遍歷
for i := 0; i < len(pointarray); i++ {
fmt.Println("Element", i, "of array is", pointarray[i])
}

//採用range遍歷
for i, v := range pointarray {
fmt.Println("Array element [", i, "]=", v)
}

 

上邊提供了數組的聲明方式 var 數組名 [數組長度] 元素類型,
同時給出了兩種數組遍歷方式:
1 len(數組名) 能夠獲取數組大小,而後遍歷
2 採用range遍歷,第一個返回值是索引,第二個返回值是對應的內容
int 類型數組初始值爲0,指針類型數組初始值爲nil
結果以下:指針

1
2
3
4
5
6
7
8
9
10
11
12
[0 0 0 0 0 0 0 0]
[<nil> <nil> <nil> <nil>]
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
pointarray len is 4
Element 0 of array is <nil>
Element 1 of array is <nil>
Element 2 of array is <nil>
Element 3 of array is <nil>
Array element [ 0 ]= <nil>
Array element [ 1 ]= <nil>
Array element [ 2 ]= <nil>
Array element [ 3 ]= <nil>

 

前文說過數組是值類型變量,咱們寫個函數,在函數內部修改形參數組的變量內容,看是否會對實參影響code

1
2
3
4
5
6
7
8
9
func modify(array [5]int) {
array[0] = 200
fmt.Println("In modify(), array values:", array)
}
func main(){
array := [5]int{1, 2, 3, 4, 5}
modify(array)
fmt.Println("In main(), array values:", array)
}

 

結果以下索引

1
2
In modify(), array values: [200 2 3 4 5]
In main(), array values: [1 2 3 4 5]

 

說明實參沒有被函數修改。那麼既然golang傳遞變量的方式都是值傳遞,是否是就沒辦法經過函數修改外部變量了呢?
確定不是的,能夠經過引用類型變量修改,好比指針,slice,map,chan等均可以在函數體內修改,從而影響外部實參的內容。
下面經過slice說明這一點內存

slice切片

先看代碼ci

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
array := [5]int{1, 2, 3, 4, 5}
//根據數組生成切片
//切片
var mySlice []int = array[:3]
fmt.Println("Elements of array")
for _, v := range array {
fmt.Print(v, " ")
}
fmt.Println("\nElements of mySlice: ")
for _, v := range mySlice {
fmt.Print(v, " ")
}

//直接建立元素個數爲5的數組切片
mkslice := make([]int, 5)
fmt.Println("\n", mkslice)
//建立初始元素個數爲5的切片,元素都爲0,且預留10個元素存儲空間
mkslice2 := make([]int, 5, 10)
fmt.Println("\n", mkslice2)
mkslice3 := []int{1, 2, 3, 4, 5}
fmt.Println("\n", mkslice3)

//元素遍歷
for i := 0; i < len(mkslice3); i++ {
fmt.Println("mkslice3[", i, "] =", mkslice3[i])
}

//range 遍歷
for i, v := range mkslice3 {
fmt.Println("mkslice3[", i, "] =", v)
}

 

生成切片有三種方式

1 經過數組或者切片截取生成新的切片
2 經過make生成 如mkslice := make([]int, 5)
3 直接初始化 如mkslice3 := []int{1, 2, 3, 4, 5}
切片遍歷和數組遍歷相似,上面結果以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Elements of array
1 2 3 4 5
Elements of mySlice:
1 2 3

[0 0 0 0 0]

[0 0 0 0 0]

[1 2 3 4 5]
mkslice3[ 0 ] = 1
mkslice3[ 1 ] = 2
mkslice3[ 2 ] = 3
mkslice3[ 3 ] = 4
mkslice3[ 4 ] = 5
mkslice3[ 0 ] = 1
mkslice3[ 1 ] = 2
mkslice3[ 2 ] = 3
mkslice3[ 3 ] = 4
mkslice3[ 4 ] = 5

 

獲取切片大小和容量

1
2
3
4
//獲取size和capacity
mkslice4 := make([]int, 5, 10)
fmt.Println("len(mkslice4):", len(mkslice4))
fmt.Println("cap(mkslice4):", cap(mkslice4))

獲取大小採用len,獲取實際開闢的容量用cap

切片添加和刪除

1
2
3
4
5
6
//末尾添加三個元素
mkslice4 = append(mkslice4, 1, 2, 3)
fmt.Println("mkslice4 is : ", mkslice4)

mkslice4 = append(mkslice4, mkslice3...)
fmt.Println("mkslice4 is : ", mkslice4)

採用append 方式能夠添加切片數據,可是要注意將append賦值給要存儲結果的slice
append有兩種用法,第一種是多個參數,第一個參數是slice,後邊是要加的多個元素。
第二種是第一個參數爲slice,第二個參數爲slice展開,slice…表示把slice中元素一個個展開加入。
切片的刪除較爲麻煩,好比說刪除第n個元素,就是截取n-1以前的序列和n以後的序列進行拼接。

1
2
3
4
5
6
7
8
9
10
11
 mkslice4 := make([]int, 0)

//末尾添加三個元素
mkslice4 = append(mkslice4, 1, 2, 3)
fmt.Println("mkslice4 is : ", mkslice4)
mkslice3 := []int{1, 2, 3, 4, 5}
mkslice4 = append(mkslice4, mkslice3...)

fmt.Println("mkslice4 is : ", mkslice4)
mkslice4 = append(mkslice4[:4-1], mkslice4[4:]...)
fmt.Println("mkslice4 is : ", mkslice4)

 

切片的copy

copy函數提供了切片的深層複製,而賦值操做(=)牢牢是淺拷貝。
看看賦值操做,咱們修改slice內部元素數據,其餘slice是否會受到影響

1
2
3
4
5
6
7
8
9
10
 oldslice := []int{1, 2, 3, 4, 5}
newslice := oldslice[:3]
newslice2 := oldslice
fmt.Println("newslice is :", newslice)
fmt.Println("newslice2 is :", newslice2)
fmt.Printf("newslice addr is : %p \n", &newslice)
fmt.Printf("newslice2 addr is: %p \n", &newslice2)
oldslice[0] = 1024
fmt.Println("newslice is :", newslice)
fmt.Println("newslice2 is :", newslice2)

 

輸出一下

1
2
3
4
5
6
newslice is : [1 2 3]
newslice2 is : [1 2 3 4 5]
newslice addr is : 0xc00005a400
newslice2 addr is: 0xc00005a420
newslice is : [1024 2 3]
newslice2 is : [1024 2 3 4 5]

 

能夠看到oldslice修改後,newslice和newslice2都受到影響了,即使他們地址不一樣。
爲何呢?這要追溯到slice內部實現

1
2
3
4
5
type Slice struct {
ptr unsafe.Pointer // Array pointer
len int // slice length
cap int // slice capacity
}

 

Slice 內部其實存放了一個指針ptr,這個ptr指向的地址就是存放數據連續空間的首地址,len表示空間當前長度,cap表示空間實際開闢了多大。
以下圖
1.jpg
那如何深copy元素到另外一個slice呢?就是copy函數了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1)
fmt.Println("after copy.....")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)
slice2[0] = 1024
slice2[1] = 999
slice2[2] = 1099
fmt.Println("after change element slice2...")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)
copy(slice1, slice2)
fmt.Println("after copy.....")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)

 

結果以下

1
2
3
4
5
6
7
8
9
after copy.....
slice1: [1 2 3 4 5]
slice2: [1 2 3]
after change element slice2..
slice1: [1 2 3 4 5]
slice2: [1024 999 1099]
after copy.....
slice1: [1024 999 1099 4 5]
slice2: [1024 999 1099]

 

能夠看到copy(destslice,srcslice),當destslice 大小< srcslice時,只拷貝destslice大小的數據。
也就是說copy的大小取決於destslice和srcslice最小值
另外copy後,修改slice2元素,slice1也不會受到影響,是深copy。
感謝關注個人公衆號
wxgzh.jpg

相關文章
相關標籤/搜索