上篇主要介紹了Go語言裏面常見的複合數據類型的聲明和初始化。數組
這篇主要針對數組、切片和映射這些複合數據類型從其餘幾個方面介紹比較下。bash
不論是數組、切片仍是映射結構,都是一種集合類型,要從這些集合取出元素就要查找或者遍歷。app
對於從其餘語言轉到Go語言,在遍歷這邊仍是有稍稍不一樣的。函數
形式1ui
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}
複製代碼
這種最「老土」的遍歷形式應該是全部的語言都通用的吧。spa
形式2指針
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Println(index, value)
}
}
複製代碼
range關鍵字表示遍歷,後面在切片和映射的遍歷咱們也能夠看到。code
這個遍歷就有點Java裏面的加強for的味道了。cdn
可是還有有點不同,我前兩天剛寫Go代碼的時候還在這裏掉坑裏了。blog
for關鍵字後面有兩個變量,一個是index即數組角標表示第幾個元素,一個是value即每一個元素的值。
坑就坑在,若是隻有一個變量也是能夠遍歷的,好比這樣
func main() {
arr := [5]int{1, 2, 3, 4, 5}
for v := range arr {
fmt.Println(v)
}
}
複製代碼
這樣和Java的加強for循環遍歷幾乎就同樣了,因此我想固然的覺得這裏的v就是arr對應的每一個元素值。
但其實不是,這裏v表示的是數組角標。因此若是按照這樣的寫法本覺得取到的是數組的值,實際上是數組的角標值。
另外,Go語言中有一個特性,對於有些用不上的變量,可使用"_"代替,好比上面的代碼能夠寫成
func main() {
arr := [5]int{1, 2, 3, 4, 5}
for _, value := range arr {
fmt.Println(value)
}
}
複製代碼
切片的遍歷和數組沒有什麼區別。
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
for index, v := range s {
fmt.Println(index, v)
}
}
複製代碼
兩種遍歷方式也都是適用的。
注意這裏len函數表示獲取切片的長度,除此之外,切片還有一個數組沒有的函數即cap,cap表示切片的容量,後面在擴容部分會在提到。
相較於Java裏面對於Map遍歷與其餘集合遍歷有些差異來講,Go裏面對於Map的遍歷與其餘集合的遍歷倒顯得比較一致。
package main
import "fmt"
func main() {
m := make(map[string]string)
m["Jackie"] = "Zheng"
m["Location"] = "Shanghai"
for key, value := range m {
fmt.Println(key, value)
}
}
複製代碼
除此之外,咱們能夠只針對key進行遍歷,以下
func main() {
m := make(map[string]string)
m["Jackie"] = "Zheng"
m["Location"] = "Shanghai"
for key := range m {
fmt.Println(key, m[key])
}
}
複製代碼
數組和struct結構體都是靜態數據,數組是定長的,而切片和映射都是動態數據類型。
爲何說是動態數據類型?
上面有順帶提過,切片除了有長度len的概念,還有容量的概念。上篇說到切片聲明初始化的一種方式
s := make([]int, 3, 5) // 3所在位置表示切片長度,5所在位置表示容量即最大可能存儲的元素個數
複製代碼
咱們能夠動態向切片添加或者刪除元素。
若是新添加元素後已經超出切片原來的容量,那麼就會擴容了。借用Go聖經裏面的例子
var x, y []int
for i := 0; i < 10; i++ {
y = append(x, i)
fmt.Printf("%d cap=%d\t %v\n", i, cap(y), y)
x = y
}
複製代碼
使用append添加新元素每次都會校驗當前切片的長度若是已經達到最大容量,則會考慮先擴容,從執行結果能夠看出每次擴容是原來的兩倍,實際的擴容過程是會先建立一個兩倍長的底層數組,而後將原切片數據拷貝到這個底層數組,再添加要插入的元素。
因此,這裏append函數以後要賦值給對應的切片,由於擴容後和擴容前的內存地址變了,若是不作賦值,可能會出現使用原來的變量沒法訪問到新切片的狀況。
首先來看一個數組的例子
package main
import "fmt"
func main() {
var arr = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
fmt.Printf("origin array address: %p \n", &arr)
passArray(arr)
fmt.Println(arr)
}
func passArray (arr1 [5]int) {
fmt.Printf("passed array address, arr1: %p \n", &arr1)
fmt.Println(arr1)
arr1[3] = 111
fmt.Println("pass array arr1: ", arr1)
}
複製代碼
執行結果以下
[1 2 3 4 5]
origin array address: 0xc000090000
passed array address, arr1: 0xc000090060
[1 2 3 4 5]
pass array arr1: [1 2 3 111 5]
[1 2 3 4 5]
複製代碼
這是由於,在調用函數passArray時,傳的是arr數組的一個副本,從新開闢了一個新空間存儲這5個數組元素,不一樣內存空間的數組變更是不會影響另外一塊存儲數組元素的內存空間的。
這種數組傳遞是很是笨重的,由於須要從新開闢一塊空間把原來的數組copy一份,這裏是5個元素,若是是1000或者10000個元素呢?因此,咱們能夠經過其餘的方式規避這種笨重的操做,沒錯,就是指針,代碼以下
package main
import "fmt"
func main() {
var arr = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
fmt.Printf("origin array address: %p \n", &arr)
passAddress(&arr)
fmt.Println(arr)
}
func passAddress (arr2 *[5]int) {
fmt.Printf("passed array address, arr2: %p \n", arr2)
fmt.Printf("passed array address, arr2: %p \n", &arr2)
fmt.Println(arr2)
arr2[3] = 111
fmt.Println("pass array arr2: ", *arr2)
}
複製代碼
執行結果以下
[1 2 3 4 5]
origin array address: 0xc000084000
passed array address, arr2: 0xc000084000
passed array address, arr2: 0xc00000e010
&[1 2 3 4 5]
pass array arr2: [1 2 3 111 5]
[1 2 3 111 5]
複製代碼
原始數組的值被改變了,這是由於咱們傳遞的是一個引用,經過一個地址指向了原來數組存儲的地址。因此在函數passAddress中其實是對原來的內存空間的數據更新,顯然也會反應到原來的數組上。
如上是數組傳值的例子,slice和map也是傳值的。雖然咱們在傳遞slice或者map的時候沒有顯式使用指針,可是他們的內部結構都間接使用了指針,因此slice和map都是引用類型,傳遞的時候至關於傳遞的是指針的副本,能夠理解爲上面數組中傳指針的例子。
我的公衆號:JackieZheng 我會將我最新的文章推送給您,並和您一塊兒分享我平常閱讀過的優質文章