Go語言入門——數組、切片和映射(下)

上篇主要介紹了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]
複製代碼
  • 先打印該數組,沒有問題
  • 在打印當前數組的地址爲:0xc000090000
  • 再調用函數passArray,先打印改數組地址爲:0xc000090060,能夠看出這裏的地址和原始數組的地址不同,這是由於這裏傳的是一個數組的副本,並不是指向原數組
  • 而後打印arr1數組,和原數組數據一致
  • 再更新角標爲3的元素值爲111,打印後的結果爲:[1 2 3 111 5]。能夠發現arr1數組已經更新了
  • 調用完成passArray後,在打印原始數組,發現數據仍爲:[1 2 3 4 5]並無由於arr1的更新而受影響。

這是由於,在調用函數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]
複製代碼
  • 先打印該數組,沒有問題
  • 在打印當前數組的地址爲:0xc000084000
  • 而後調用函數passAddress,注意這裏傳的是數組的地址,接收的是一個指針類型變量arr2。第一次咱們直接打印arr2,獲得地址爲:0xc000084000。沒錯,這裏的意思是arr2這個指針指向的內存地址就是0xc000084000,即和原始數組指向的是同一塊內存區域,也就是指向同一塊存儲這5個元素的區域。
  • 緊接着,打印arr2的地址,這個&arr2的意思是arr2這個指針的地址,爲0xc00000e010,經過上面一點,咱們已經知道這個指針指向的地址是0xc000084000
  • 而後咱們打印arr2,獲得&[1 2 3 4 5]
  • 以後咱們再改變第三個角標的值爲111,並打印arr2指針指向的數組的值爲:[1 2 3 111 5],即arr2中元素已經更新
  • 調用完passAddress後,咱們再次打印原始數組,獲得的是:[1 2 3 111 5]

原始數組的值被改變了,這是由於咱們傳遞的是一個引用,經過一個地址指向了原來數組存儲的地址。因此在函數passAddress中其實是對原來的內存空間的數據更新,顯然也會反應到原來的數組上。

如上是數組傳值的例子,slice和map也是傳值的。雖然咱們在傳遞slice或者map的時候沒有顯式使用指針,可是他們的內部結構都間接使用了指針,因此slice和map都是引用類型,傳遞的時候至關於傳遞的是指針的副本,能夠理解爲上面數組中傳指針的例子。

我的公衆號:JackieZheng 我會將我最新的文章推送給您,並和您一塊兒分享我平常閱讀過的優質文章

相關文章
相關標籤/搜索