天天看點

【Golang基礎篇】——array、slice、指針、map

背景

每一門開發語言的基礎都是從資料類型開始學起,Java轉成Golang,是以小編的學習之路又從零開始了。Golang和其他開發語言一樣分為資料類型分為兩種值類型和引用類型,值類型比較簡單就是一些基本資料類型,無論是否有過其他語言基礎,大概看一下也是可以明白的,是以本文主要介紹Golang的引用類型。

基礎

值類型:變量直接存儲值,内容通常在棧中配置設定

引用類型:變量存儲的是一個位址,這個位址存儲最終的值,内容通常在堆上配置設定,通過GC回收

從Golang的設計思想上來說,Golang是函數式程式設計,函數式程式設計最明顯的特點不可變,是以Golang的傳參特點值傳遞,預設傳遞是值,那怎麼處理引用傳遞呢?主要利用本文講解的這幾種引用類型。

Golang中引用類型:指針、slice(切片)、map、chan,chan和并發程式設計聯系比較緊密,放到後面的并發程式設計中,主要講解指針、slice、map

數組

1. 數組:是同一種資料類型的固定長度的序列。
 2. 數組定義:var a [len]int,比如:var a [5]int,數組長度必須是常量,且是類型的組成部分。一旦定義,長度不能變。
 3. 長度是數組類型的一部分,是以,var a[5] int和var a[10]int是不同的類型。
 4. 數組可以通過下标進行通路,下标是從0開始,最後一個元素下标是:len-1
    for i := 0; i < len(a); i++ {
    }
    for index, v := range a {
    }
 5. 通路越界,如果下标在數組合法範圍之外,則觸發通路越界,會panic
 6. 數組是值類型,指派和傳參會複制整個數組,而不是指針。是以改變副本的值,不會改變本身的值。
 7.支援 "=="、"!=" 操作符,因為記憶體總是被初始化過的。
 8.指針數組 [n]*T,數組指針 *[n]T。      

數組初始化方式

package main

import (
    "fmt"
)
//全局
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}

func main() {
    //局部
    a := [3]int{1, 2}           // 未初始化元素值為 0。
    b := [...]int{1, 2, 3, 4}   // 通過初始化值确定數組長度。
    c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
    d := [...]struct {
        name string
        age  uint8
    }{
        {"user1", 10}, // 可省略元素類型。
        {"user2", 20}, // 别忘了最後一行的逗号。
    }
    //列印全局
    fmt.Println(arr0, arr1, arr2, str)
    //列印局部
    fmt.Println(a, b, c, d)
}      

注意點

1、數組是值類型,指派和傳參會複制整個數組,而不是指針。是以改變副本的值,不會改變本身的值。

package main

import (
    "fmt"
)

func test(x [2]int) {
    fmt.Printf("x: %p\n", &x)
    x[1] = 1000
}

func main() {
    a := [2]int{}
    fmt.Printf("a: %p\n", &a)

    test(a)
    fmt.Println(a)
}

//列印結果
a: 0xc42007c010
x: 0xc42007c030
[0 0]      

通過這段代碼可以推出數組是值類型,在傳輸的過程中都是複制,記憶體位址已經不同。

值拷貝行為會造成性能問題,如何解決這個問題,則引入slice,和指針

slice(切片)

slice 并不是數組或數組指針。它通過内部指針和相關屬性引用數組片段,以實作變長方案。

1. 切片:切片是數組的一個引用,是以切片是引用類型。但自身是結構體,值拷貝傳遞。
2. 切片的長度可以改變,是以,切片是一個可變的數組。
3. 切片周遊方式和數組一樣,可以用len()求長度。表示可用元素數量,讀寫操作不能超過該限制。 
4. cap可以求出slice最大擴張容量,不能超出數組限制。0 <= len(slice) <= len(array),其中array是slice引用的數組。
5. 切片的定義:var 變量名 []類型,比如 var str []string  var arr []int。
6. 如果 slice == nil,那麼 len、cap 結果都等于 0。      

建立切片的方式

//1.聲明切片
   var s1 []int
   if s1 == nil {
      fmt.Println("是空")
   } else {
      fmt.Println("不是空")
   }
   // 2.:=
   s2 := []int{}
   // 3.make()
   var s3 []int = make([]int, 0)

  //make建立具體參數明顯
   var slice []type = make([]type, len)
   slice  := make([]type, len)
   slice  := make([]type, len, cap)

   s1 := []int{0, 1, 2, 3, 8: 100} // 通過初始化表達式構造,可使用索引号。
   fmt.Println(s1, len(s1), cap(s1))

   s2 := make([]int, 6, 8) // 使用 make 建立,指定 len 和 cap 值。
   fmt.Println(s2, len(s2), cap(s2))

   s3 := make([]int, 6) // 省略 cap,相當于 cap = len。      

make建立切片的記憶體配置設定

【Golang基礎篇】——array、slice、指針、map

 切片初始化

全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end] 
var slice1 []int = arr[:end]        
var slice2 []int = arr[start:]        
var slice3 []int = arr[:] 
var slice4 = arr[:len(arr)-1]      //去掉切片的最後一個元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]        
slice7 := arr[start:]     
slice8 := arr[:]  
slice9 := arr[:len(arr)-1] //去掉切片的最後一個元素      
【Golang基礎篇】——array、slice、指針、map

數組和切片的記憶體布局

【Golang基礎篇】——array、slice、指針、map

使用 make 動态建立slice,避免了數組必須用常量做長度的麻煩。還可用指針直接通路底層數組,退化成普通數組操作。

package main

import "fmt"

func main() {
    s := []int{0, 1, 2, 3}
    p := &s[2] // *int, 擷取底層數組元素指針。
    *p += 100

    fmt.Println(s)
}

輸出結果:

    [0 1 102 3]      

用append内置函數操作切片

append :向 slice 尾部添加資料,傳回新的 slice 對象。 

package main

import (
    "fmt"
)

func main() {

    s1 := make([]int, 0, 5)
    fmt.Printf("%p\n", &s1)

    s2 := append(s1, 1)
    fmt.Printf("%p\n", &s2)

    fmt.Println(s1, s2)

}

輸出結果:

    0xc42000a060
    0xc42000a080
    [] [1]      

超出原 slice.cap 限制,就會重新配置設定底層數組,即便原數組并未填滿

package main

import (
    "fmt"
)

func main() {

    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]

    s = append(s, 100, 200) // 一次 append 兩個值,超出 s.cap 限制。

    fmt.Println(s, data)         // 重新配置設定底層數組,與原數組無關。
    fmt.Println(&s[0], &data[0]) // 比對底層數組起始指針。

}
輸出結果
[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
    0xc4200160f0 0xc420070060      

指針

Go語言中的函數傳參都是值拷貝,當我們想要修改某個變量的時候,我們可以建立一個指向該變量位址的指針變量。傳遞資料使用指針,而無須拷貝資料。

Go語言中的指針操作非常簡單,隻需要記住兩個符号:​

​&​

​​(取位址)和​

​*​

​(根據位址取值)。

每個變量在運作時都擁有一個位址,這個位址代表變量在記憶體中的位置。Go語言中使用&字元放在變量前面對變量進行“取位址”操作。 Go語言中的值類型​

​(int、float、bool、string、array、struct)​

​​都有對應的指針類型,如:​

​*int、*int64、*string​

​等。

變量指針

ptr := &v    // v的類型為T
v:代表被取位址的變量,類型為T
ptr:用于接收位址的變量,ptr的類型就為*T,稱做T的指針類型。*代表指針。      

從記憶體角度看下差別

func main() {
    a := 10
    b := &a
    fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
    fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
    fmt.Println(&b)                    // 0xc00000e018
}      
【Golang基礎篇】——array、slice、指針、map

可以看到b的實際存儲的是a的變量的記憶體位址,是以b被稱為指針類型,那麼如何擷取指針類型真正的資料值呢?即指針取值

func main() {
    //指針取值
    a := 10
    b := &a // 取變量a的位址,将指針儲存到b中
    fmt.Printf("type of b:%T\n", b)
    c := *b // 指針取值(根據指針去記憶體取值)
    fmt.Printf("type of c:%T\n", c)
    fmt.Printf("value of c:%v\n", c)
}

type of b:*int
type of c:int
value of c:10      

總結: 取位址操作符&和取值操作符​

​*​

​​是一對互補操作符,​

​&​

​​取出位址,​

​*​

​根據位址取出位址指向的值。

變量、指針位址、指針變量、取位址、取值的互相關系和特性如下:

1.對變量進行取位址(&)操作,可以獲得這個變量的指針變量。
    2.指針變量的值是指針位址。
    3.對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值。      

空指針是所有程式員逃不開的困難,Go中何為空指針

  • 當一個指針被定義後沒有配置設定到任何變量時,它的值為 nil
  • 空指針的判斷,判斷是為nil即可

new和make的使用

new是一個内置的函數,它的函數簽名如下:
func new(Type) *Type

1.Type表示類型,new函數隻接受一個參數,這個參數是一個類型
2.*Type表示類型指針,new函數傳回一個指向該類型記憶體位址的指針。

new函數不太常用,使用new函數得到的是一個類型的指針,并且該指針對應的值為該類型的零值      
make也是用于記憶體配置設定的,差別于new,它隻用于slice、map以及chan的記憶體建立,而且它傳回的類型就是這三個類型本身,而不是他們的指針類型,因為這三種類型就是引用類型,是以就沒有必要傳回他們的指針了

func make(t Type, size ...IntegerType) Type

make函數是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然後才可以對它們進行操作。

var b map[string]int   // 隻是聲明變量b是一個map類型的變量 此時b=nil
b = make(map[string]int, 10) //需要make進行初始化後才能使用,否會panic      

new和make的差別

1.二者都是用來做記憶體配置設定的。
  2.make隻用于slice、map以及channel的初始化,傳回的還是這三個引用類型本身;
  3.而new用于類型的記憶體配置設定,并且記憶體對應的值為類型零值,傳回的是指向類型的指針。      

map

//Go中map定義 
map[KeyType]ValueType

KeyType:表示鍵的類型。
ValueType:表示鍵對應的值的類型。

map類型的變量預設初始值為nil,需要使用make()函數來配置設定記憶體
make(map[KeyType]ValueType, [cap])
cap是make的容量,非必填,但是我們應該在初始化map的時候就為其指定一個合适的容量      

map的基本使用

func main() {
    //填充
    scoreMap := make(map[string]int, 8)
    scoreMap["張三"] = 90
    scoreMap["小明"] = 100
    fmt.Println(scoreMap)
    fmt.Println(scoreMap["小明"])
    fmt.Printf("type of a:%T\n", scoreMap)
    //在聲明時填充
    userInfo := map[string]string{
        "username": "pprof.cn",
        "password": "123456",
    }
    
    //判斷key是否存在,定義 value, ok := map[key]
    // 如果key存在ok為true,v為對應的值;不存在ok為false,v為值類型的零值
    v, ok := scoreMap["張三"]
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("查無此人")
    }
    
    //map周遊 range
    //同時需要k,v
    for k, v := range scoreMap {
        fmt.Println(k, v)
    }
    //隻需要k
    for k := range scoreMap {
        fmt.Println(k)
    }

    //删除指定key、delete(map, key)
    //map:表示要删除鍵值對的map
    //key:表示要删除的鍵值對的鍵
    delete(scoreMap, "小明")//将小明:100從map中删除
    
}      

總結

繼續閱讀