Go語言 4 數組、切片和映射


文章由作者馬志國在博客園的原創,若轉載請於明顯處標記出處:http://www.cnblogs.com/mazg/

 數組是由同構的元素組成。結構體是由異構的元素組成。數據和結構體都是有固定內存大小的數據結構。相比之下,切片和映射則是動態的數據結構,它們根據需要動態增長。

4.1 數組

數組是一系列同一類型數據的集合,數組中包含的每個數據被稱為數組元素。一個數組包含的元素個數稱為數組的長度,數組長度是固定的。一個數組可以由零個或多個元素組成。

1 數組聲明

數組聲明的一般形式:

var 數組名 [長度]類型

示例如下:

var arr [10]int           //10個元素的整型數組

var ptrs [5]*float64  //5個元素的指針數組,每個指針都指向float64類型 

var points [8]struct{ x, y int }  //8個元素的結構體類型

var arry [2][3]int               //2*3的二維整型數組 

2 簡短聲明

與變量的簡短聲明一樣,數組也可以簡短聲明。如果在數組的長度位置出現的是...”省略號,則表示數據的長度是根據初始化值得個數來計算。

a := [3]int{1, 2, 3} // 長度為3的數組

b := [5]int{1, 2, 3} //長度為10,前三個元素為1、2、3,其它默認為0

c := [...]int{4, 5, 6} //長度3的方式,Go自動計算長度

r := [...]int{9: 6}    //長度為10,最后一個元素的值為6,其它默認為0

arr2 := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}//二維數組

3 元素訪問

數組的每個元素通過索引下標來訪問,內置的len函數將返回數組中元素的個數。

arr := [...]int{1, 2, 3, 4, 5}

len := len(arr)   //len獲取數組長度

fmt.Println("修改前:", arr)

arr[0] = 100      //下標訪問數組元素

fmt.Println("修改后:", arr)

fmt.Println("數組長度:", len)

打印結果:

修改前: [1 2 3 4 5]

修改后: [100 2 3 4 5]

數組長度: 5

4 數組遍歷

兩種遍歷方式,其中range表達式遍歷有兩個返回值,第一個是索引,第二個是元素的值。示例如下:

arr := [...]int{1, 2, 3, 4, 5}

len := len(arr) //len獲取數組長度

for i := 0; i < len; i++ {

  fmt.Println(i, arr[i])

}

for i, v := range arr {

  fmt.Println(i, v)

}

 

5 作為函數參數

Go語言中,數組作為函數的參數仍然是值傳遞,雖然可以使用數組的指針來代替,但是改變不了數組長度。這時,我們通常使用切片slice來替代數組。

 

6 數組比較

如果數組元素的類型是可比較的,那么這個數組也是可的比較。只有數組的所有元素都相等數組才是相等的。由於長度也是數組類型的一部分,所以長度不同的數組是不等的。

數組可遍歷、可修改,是否可比較,由數組元素決定。%T用於顯示一個值對應的數據類型。

 

4.2 數組切片(slice

數組的長度在定義之后無法修改。數組是值類型,每次傳遞都將產生一份副本。顯然這無法滿足開發者的某些需求。Go語言提供了數組切片(slice)來彌補數組的不足。數組和slice之間有着緊密的聯系,一個slice是一個輕量級的數據結構,提供了訪問數組子序列元素的功能,而且slice的底層確實引用一個數組對象。

1 聲明數組切片(slice

    數組切片與數組聲明非常相似,唯一區別是無需指定長度。

var 數組切片 []類型

var slice []int

2 基於數組以及基於切片創建切片

arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //數組

slice1 := arr[0:5]     //基於數組切片1,左閉右開

slice2 := slice1[1:3]  //基於切片1創建切片2

fmt.Println(slice1)    //[1 2 3 4 5]

fmt.Println(slice2)    //[2 3]

3 直接創建切片

Go語言提供的內置函數make()可以用於靈活的創建數組切片。make()函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情況下,容量將等於長度。在底層,make創建了一個匿名的數組變量,然后返回一個slice。只有通過返回的slice才能引用匿名的數組變量。

make([]T,len)

make([]T,len,cap)

內置的len()函數獲取長度,內置的cap()函數獲取容量。

slice1 := make([]int, 5)

slice2 := make([]int, 5, 10)

slice3 := []int{1, 2, 3, 4, 5}

fmt.Println(slice1, len(slice1), cap(slice1))

fmt.Println(slice2, len(slice2), cap(slice2))

fmt.Println(slice3, len(slice3), cap(slice3))

打印結果:

[0 0 0 0 0] 5 5

[0 0 0 0 0] 5 10

[1 2 3 4 5] 5 5

與數組相同,slice操作不能超出len指定的范圍。

slice := make([]int, 5, 10)

slice[3] = 10 //正確

slice[8] = 8 //錯誤 ,索引超出范圍

 

 

4 切片的遍歷

切片的遍歷與數組的遍歷方式相同。

5 切片不能比較

與數組不同的是slice之間不能比較,因此我們不能使用==操作符來判斷兩個slice是否含有全部相等的元素。不過標准庫提供了高度優化的bytes.Equal函數兩個字節型slice是否相等,但是對於其它類型的slice,我們必須自己展開每個元素進行比較。

切片可遍歷,可修改,不可比較

6 判斷切片是否為空

使用len(s)==0來判斷一個slice是否為空。

7 追加元素

內置的append函數用於向slice追加元素。可以直接追加元素,也可以追加一個slice。注意參數slice后有...。否則有語法錯誤。因為append()函數的語義是從第二個參數開始都應該是待附加的元素。slice后加...意味將slice的元素全部打散后傳入。數組切片會自動處理存儲空間不足的問題。如果追加的內容長度超過當前已分配的存儲空間(即cap()調用返回的信息),數組切片會自動分配一塊足夠大的內存。 

slice := make([]int, 5, 10)

slice = append(slice, 1, 2, 3)

fmt.Println(slice)

slice2 := []int{4, 5, 6}

slice = append(slice, slice2...)

fmt.Println(slice)

打印結果:

[0 0 0 0 0 1 2 3]

[0 0 0 0 0 1 2 3 4 5 6]

 

8 切片復制

內置的copy函數用於數組切片的復制。復制時無需考慮目標數組和源數組的長度。

slice1 := []int{1, 2, 3, 4, 5}

slice2 := []int{7, 8, 9}

copy(slice2, slice1) //只會將slice1的前3個元素賦值給slice2

fmt.Println(slice2)

slice3 := []int{1, 2, 3, 4, 5}

slice4 := []int{7, 8, 9}

copy(slice3, slice4) //將slice4的元素賦值slice3的前3個元素

fmt.Println(slice3)

打印結果:

[1 2 3]

[7 8 9 4 5]

9 作為函數參數時切片與數組的區別

func SetValueByArray(arr [5]int) {

    arr[0] = 100

}

 

func SetValueBySlice(slice []int) {

    slice[0] = 100

}

func main() {

    arr := [5]int{1, 2, 3, 4, 5}

    SetValueByArray(arr)

    fmt.Println(arr)  

    slice := arr[:]

    SetValueBySlice(slice)

    fmt.Println(arr)

}

//打印結果:

[1 2 3 4 5]

[100 2 3 4 5]

 

10字符串和byte切片

標准庫中提供了4個與字符串操作相關的包:

功能

strings

提供了字符串查詢、替換、比較、截斷、拆分和合並等功能。

bytes

提供了很多與strings包類似的功能。因為字符串是只讀的,逐步構建字符串會導致很多分配和復制,這種情況下,使用bytes.Buffer類型將會更有效。

strconv

提供了布爾類型、整數、浮點數和對應字符串的相互轉換,還提供了雙引號轉義相關的轉換。

unicode

提供了IsDigitIsLetterIsUpperIsLower等功能,用於給字符分類。

 

strings包常用的6個函數,功能參考Go中文幫助文檔

func Contains(s, substr string) bool

func Count(s, sep string) int

func Fields(s string) []string

func HasPrefix(s, prefix string) bool

func Index(s, sep string) int

func Join(a []string, sep string) string

 

byte包常用的的6個函數,功能與strings類似。

func Contains(b, subslice []byte) bool

func Count(s, sep []byte) int

func Fields(s []byte) [][]byte

func HasPrefix(s, prefix []byte) bool

func Index(s, sep []byte) int

func Join(s [][]byte, sep []byte) []byte

它們之間的唯一區別就是字符串類型的參數替換成了字節slice類型的參數。

4.3 映射

映射是一個無序的鍵值對集合,其中所有的鍵都是不同的,然后通過給定的鍵可以在常數時間復雜度內檢索、更新或刪除對應的值。在Go語言中,一個map就是一個哈希表的引用,映射中所有的鍵都有相同的類型,所有的值也有着相同的類型,但是鍵和值之間可以是不同的數據類型。

1 聲明映射

聲明的一般格式為:

var 映射名稱 map[]

只是聲明一個map,它的初始值是nil,也就是沒有引用任何哈希表。所以不能向一個nil值的map存入元素。

var ages map[string]int

fmt.Println(age == nil)//”true” 

fmt.Println(len(ages)== 0)//”true” 

  2 創建映射

內置的make函數可以創建一個map,創建后可以存入元素。

myMap1 := make(map[string]int)      //創建一個map

myMap2 := make(map[string]int, 100) //創建一個map,初始儲存能力為100

myMap3 := map[string]int{

     "str1": 10, "str2": 20, "str3": 30} //直接創建,並初始化

fmt.Println(myMap1, len(myMap1))

fmt.Println(myMap2, len(myMap2))

fmt.Println(myMap3, len(myMap3))

打印結果:

map[] 0

map[] 0

map[str3:30 str1:10 str2:20] 3

 

3  元素的賦值和刪除

  元素可以直接賦值,內置的delete函數用於刪除元素,刪除一個不存在的元素,不會造成錯誤。

 myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30} 

fmt.Println(myMap3, len(myMap3))

myMap3["str5"] = 50

fmt.Println(myMap3, len(myMap3))

delete(myMap3, "str")

delete(myMap3, "str3")

fmt.Println(myMap3, len(myMap3))

打印結果:

map[str3:30 str1:10 str2:20] 3

map[str5:50 str1:10 str2:20 str3:30] 4

map[str1:10 str2:20 str5:50] 3

 

4 查找元素

有時候可能需要知道對應的元素是否在map中,例如,如果元素類型是一個布爾類型,你可能需要區分一個零值的元素,和因為元素不存在而返回的零值,可以像下面這樣使用:

v,ok:=map[key]

if !ok{/*在map中不存在key為鍵的元素*/}

 

//結合起來使用

if v,ok:=map[key];!ok{/*    */ }

在這種場景下,map下標語法將產生兩個值;第二個值是一個布爾類型,用於表示元素是否存在。布爾變量一般命名為ok。示例如下:

myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30}

if v, ok := myMap3["str2"]; ok {

  fmt.Println(v)

}

 

5 遍歷映射

遍歷map使用range風格的for循環實現。由於map是無序的集合,所以每次遍歷的順序可能不一樣。

myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30}

for k, v := range myMap3 {

   fmt.Println(k, v)

}

6 映射不可比較

slice一樣,map之前也不能進行相等比較;唯一的例外是和nil進行比較。要判斷兩個map是否包含相同的keyvalue,我們必須通過一個循環實現。有時候,我們需要一個mapkeyslice類型,但是mapkey必須是可比較的類型,但是slice並不滿足這個條件。我們可以通過兩個步驟繞過這個限制。第一步,定義一個輔助函數k,將slice轉為map對應的string類型的key,確保只有xy相等時,k(x)==k(y)才成立。然后創建一個keystring類型的map,在每次對map操作時,先用k輔助函數將slice轉化為string類型。

7 不能對映射元素取址操作

map中的元素並不是一個變量,不能對map元素進行取址操作。禁止對map元素取址的原因是map可能隨着元素數量的增長而重新分配更大的內存空間,從而可能導致之前的地址無效。slice元素可以取址操作。

fmt.Println(&myMap3["str1"])//錯誤,不能取址操作

8 nil值映射

map上的大部分操作,包括查找、刪除、lenrange循環都可以安全工作在nil值的map上,它們的行為和一個空map類似。但是向一個nil值的map存入元素將導致一個panic異常。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM