Go語言快速入門指針Map使用示例教程

1. 指針

區別於C/C++中的指針,Go語言中的指針不能進行偏移和運算,是安全指針。

要搞明白Go語言中的指針需要先知道3個概念:指針地址、指針類型和指針取值。

Go語言中的函數傳參都是值拷貝,當我們想要修改某個變量的時候,我們可以創建一個指向該變量地址的指針變量。

傳遞數據使用指針,而無須拷貝數據。類型指針不能進行偏移和運算。

Go語言中的指針操作非常簡單,隻需要記住兩個符號:&(取地址)和*(根據地址取值)。

1.1 指針地址和指針類型

每個變量在運行時都擁有一個地址,這個地址代表變量在內存中的位置。Go語言中使用&字符放在變量前面對變量進行“取地址”操作。

Go語言中的值類型(intfloatboolstringarraystruct)都有對應的指針類型,如:*int*int64*string等。

取變量指針的語法如下:

ptr := &v    // v的類型為T

其中:

  • v:代表被取地址的變量,類型為T
  • ptr:用於接收地址的變量,ptr的類型就為*T,稱做T的指針類型。*代表指針。
package main
import "fmt"
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
}

1.2 指針取值

在對普通變量使用&操作符取地址後會獲得這個變量的指針,然後可以對指針使用*操作,也就是指針取值。

package main
import "fmt"
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

取地址操作符&和取值操作符*是一對互補操作符,&取出地址,*根據地址取出地址指向的值。

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

  • 對變量進行取地址(&)操作,可以獲得這個變量的指針變量。
  • 指針變量的值是指針地址。
  • 對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值。
package main
import "fmt"
func p1(n int) {
    n = 100
}
func p2(n *int) {
    *n = 100
}
func main() {
    a := 10
    p1(a)
    fmt.Println(a) // 10
    p2(&a)
    fmt.Println(a) // 100
}

1.3 空指針

  • 當一個指針被定義後沒有分配到任何變量時,它的值為 nil
  • 空指針的判斷
package main
import "fmt"
func main() {
    var p *string
    fmt.Printf("p的值是%v \n", p)
    if p != nil {
        fmt.Println("非空指針")
    } else {
        fmt.Println("空指針")
    }
}

1.4 new 的使用

new是一個內置的函數,它的函數簽名如下:

func new(Type) *Type

其中:

  • Type表示類型,new函數隻接受一個參數,這個參數是一個類型
  • *Type表示類型指針,new函數返回一個指向該類型內存地址的指針。

new函數不太常用,使用new函數得到的是一個類型的指針,並且該指針對應的值為該類型的零值。

func main() {
    a := new(int)
    b := new(bool)
    fmt.Printf("%T\n", a) // *int
    fmt.Printf("%T\n", b) // *bool
    fmt.Println(*a)       // 0
    fmt.Println(*b)       // false
}

var a *int隻是聲明瞭一個指針變量a但是沒有初始化,指針作為引用類型需要初始化後才會擁有內存空間,才可以給它賦值。應該按照如下方式使用內置的new函數對a進行初始化之後就可以正常對其賦值瞭:

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

make也是用於內存分配的,區別於new,它隻用於slicemap以及chan的內存創建,而且它返回的類型就是這三個類型本身,而不是他們的指針類型,因為這三種類型就是引用類型,所以就沒有必要返回他們的指針瞭。

1.5 new與make的區別

  • 二者都是用來做內存分配的。
  • make隻用於slicemap以及channel的初始化,返回的還是這三個引用類型本身;
  • new用於類型的內存分配,並且內存對應的值為類型零值,返回的是指向類型的指針。

2. Map

map是一種無序的基於key-value的數據結構,Go語言中的map是引用類型,必須初始化才能使用。

2.1 什麼是Map

key,value存儲

最通俗的話說:Map是一種通過key來獲取value的一個數據結構,其底層存儲方式為數組,在存儲時key不能重復,當key重復時,value進行覆蓋,我們通過key進行hash運算(可以簡單理解為把key轉化為一個整形數字)然後對數組的長度取餘,得到key存儲在數組的哪個下標位置,最後將keyvalue組裝為一個結構體,放入數組下標處。

hash沖突

數組一個下標處隻能存儲一個元素,也就是說一個數組下標隻能存儲一對keyvaluehashkey(xiaoming)=4占用瞭下標0的位置,假設我們遇到另一個keyhashkey(xiaowang)也是4,這就是hash沖突(不同的key經過hash之後得到的值一樣),那麼key=xiaowang的怎麼存儲?

hash沖突的常見解決方法

  • 開放定址法: 也就是說當我們存儲一個keyvalue時,發現hashkey(key)的下標已經被別key占用,那我們在這個數組中空間中重新找一個沒被占用的存儲這個沖突的key,那麼沒被占用的有很多,找哪個好呢?常見的有:線性探測法,線性補償探測法,隨機探測法,這裡以線性探測為對比。
  • 拉鏈法: 何為拉鏈,簡單理解為鏈表,當keyhash沖突時,我們在沖突位置的元素上形成一個鏈表,通過指針互連接,當查找時,發現key沖突,順著鏈表一直往下找,直到鏈表的尾節點,找不到則返回空。

開放定址(線性探測)和拉鏈的優缺點

  • 拉鏈法比線性探測處理簡單
  • 線性探測查找是會被拉鏈法會更消耗時間
  • 線性探測會更加容易導致擴容,而拉鏈不會
  • 拉鏈存儲瞭指針,所以空間上會比線性探測占用多一點
  • 拉鏈是動態申請存儲空間的,所以更適合鏈長不確定的

2.2 Map 定義

Go語言中 Map的定義語法如下:

map[KeyType]ValueType

其中:

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

map類型的變量默認初始值為nil,需要使用make()函數來分配內存。語法為:

 make(map[KeyType]ValueType, [cap])

其中cap表示map的容量,該參數雖然不是必須的,但是我們應該在初始化map的時候就為其指定一個合適的容量。

2.3 map基本使用

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)
}

輸出結果:

map[李四:100 張三:90]
100
type of a: map[string]int

map也支持在聲明的時候填充元素:

func main() {
    userInfo := map[string]string{
        "username": "admin",
        "password": "123456",
    }
    fmt.Println(userInfo)
}

2.4 map的遍歷

Go語言中使用for range遍歷map:

func main() {
    scoreMap := make(map[string]int)
    scoreMap["張三"] = 90
    scoreMap["李四"] = 100
    scoreMap["王五"] = 60
    for k, v := range scoreMap {
        fmt.Println(k, v)
    }
}

如果隻想遍歷key的時候,可以按下面的寫法:

func main() {
    scoreMap := make(map[string]int)
    scoreMap["張三"] = 90
    scoreMap["李四"] = 100
    scoreMap["王五"] = 60
    for k := range scoreMap {
        fmt.Println(k)
    }
}

註意: 遍歷map時的元素順序與添加鍵值對的順序無關。

2.5 map判斷某個鍵是否存在

Go語言中有個判斷map中鍵是否存在的特殊寫法,格式如下:

value, ok := map[key]

如果key存在oktrue,value為對應的值;不存在okfalse,value為值類型的零值

func main() {
    scoreMap := make(map[string]int)
    scoreMap["張三"] = 90
    scoreMap["李四"] = 100
    // 如果key存在ok為true,value為對應的值;不存在ok為false,value為值類型的零值
    value, ok := scoreMap["張三"]
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("查無此人")
    }
}

2.6 map使用delete()函數刪除鍵值對

使用delete()內建函數從map中刪除一組鍵值對, delete()函數的格式如下:

delete(map, key)

其中:

  • map: 表示要刪除鍵值對的map
  • key: 表示要刪除的鍵值對的鍵
func main(){
    scoreMap := make(map[string]int)
    scoreMap["張三"] = 90
    scoreMap["李四"] = 100
    scoreMap["王五"] = 60
    delete(scoreMap, "李四")//將李四: 100從 map 中刪除
    for k,v := range scoreMap{
        fmt.Println(k, v)
    }
}

以上就是Go語言快速入門指針Map使用示例教程的詳細內容,更多關於Go語言入門指針Map教程的資料請關註WalkonNet其它相關文章!

推薦閱讀: