本文參考:https://www.liwenzhou.com/posts/Go/08_map/
MAP(映射)
Go語言中提供的映射關系容器為map
,其內部使用散列表(hash)
實現。(類似於Python中的字典dict)
映射概述
map是一種無序的基於key-value
的數據結構,Go語言中map是引用類型,必須初始化后才能使用。
創建map
Go語言中map
的定義語法如下:
map[keyType]valueType
//keyType 表示鍵的類型
//valueType表示鍵對應值的類型
map類型的變量默認初始值為nil,需要使用make()函數來分配內存。
make(map[keyType]ValueType,cap)
// cap表示map的容量,不指定時默認為0,但是我們應該在初始化map的時候就為其指定一個合適的容量
示例:
func main(){
// 在聲明的時候填充元素
userInfo := map[string]string{
"username":"Negan"
"password":"mima"
}
// 使用內建函數make()創建
scoreMap:=make(map[string]int,8)
scoreMap["張三"] = 90
scoreMap["李四"] = 100
fmt.Println(scoreMap) //map[張三:90 李四:100]
fmt.Println(scoreMap["李四"]) //100
fmt.Printf("type of a:%T\n",scoreMap) //type of a:map[string]int
//創建一個string為鍵,值為任意類型的map
userMap := make(map[string]interface{})
userMap["name"] = "Negan"
userMap["age"] = 68
}
判斷某個鍵是否存在
Go語言中有個判斷map中鍵是否存在的特殊寫法
value,ok:=map[key]
示例:
func main(){
scoreMap := make(map[string]int)
scoreMap["張三"] = 90
scoreMap["李四"] = 100
// 如果key存在ok為true,v為對應的值,不存在ok為false,v為值類型的零值
v,ok := scoreMap["張三"]
if ok{
fmt.Println(v)
}else{
fmt.Println("查無此人")
}
}
map的遍歷
range關鍵字對map遍歷,可以同時得到鍵值對的key和value,也可僅僅得到key
func main(){
scoreMap := make(map[string]int)
scoreMap["張三"] = 90
scoreMap["李四"] = 100
// 遍歷key和value
for k,v := range scoreMap{
fmt.Println(k,v)
}
//僅僅遍歷key
for k := range scoreMap{
fmt.Println(k)
}
}
- 注意:遍歷map時的元素順序與添加鍵值對的順序無關。
按照指定循序遍歷map
func main(){
rand.Seed(time.Now().UnixNano()) // 初始化隨機數種子
scoreMap := make(map[string]int,200)
for i:=0;i<100;i++{
key := fmt.Sprintf("stu%02d",i) // 生成stu開頭的字符串
value := rand.Intn(100) // 生成0-99的隨機整數
scoreMap[key] = value
}
// 取出map中所有的key存放到切片keys中
keys := make([]string,0,200)
for key:=range scoreMap{
keys = append(keys, key)
}
// 對切片進行排序
sort.Strings(keys)
// 按照排序后的key遍歷map
for _,key := range keys{
fmt.Println(key, scoreMap[key])
}
}
使用delete()函數刪除鍵值對
使用delete()
內建函數從map中刪除一組鍵值對,delete()
函數的格式如下:
delete(map,key)
// map:表示要刪除鍵值對的map
// key:表示要刪除的鍵值對的鍵
示例:
func main(){
scoreMap := make(map[string]int)
scoreMap["張三"] = 90
scoreMap["李四"] = 100
delete(scoreMap,"張三") // 將張三:90從map中刪除
fmt.Println(scoreMap)
}
元素為map類型的切片
下面代碼延時了切片中的元素為map類型時的操作:
func main(){
var mapSlice = make([]map[string]string,3)
for index,value := range mapSlice{
fmt.Printf("index:%d value:%v\n",index,value)
}
fmt.Println("after init")
// 對切片中的map元素進行初始化
mapSlice[0] = make(map[string]interface{},10)
mapSlice[0]["name"] = "Negan"
mapSlice[0]["age"] = 68
mapSlice[0]["hobby"] = "棒球"
for index,value:= range mapSlice{
fmt.Printf("index:%d value:%v\n",index, value)
}
}
值為切片類型的map
下面代碼演示map中值為切片類型的操作
func main{
sliceMap := make(map[string][]string,3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中國"
value,ok := sliceMap[key]
if !ok{
value = make([]string,0,2)
}
value = append(value, "北京","上海","西安")
sliceMap[key = value
fmt.Println(sliceMap)
}
練習題
1、寫一個程序,統計一個字符串中每個單詞出現的次數。比如:“how do you do”中how=1 do=2 you=1。
func main(){
s := “how do you do”
s_sclice := strings.Split(s," ") // 分割字符串
count_map := make(map[string]int)
for key := range s_sclice{
_,ok:=count_map[key]
if !ok{
count_map[key] = 1
}else{
count_map[key] += 1
}
}
fmtPrintln(count_map)
}
指針
Go中的指針區別於C/C++中的指針,Go語言中的指針不能進行便宜和運算,是安全指針。
任何數據載入內存后,在內存都有它們的地址,這就是指針。為了保存一個數據在內存中的地址,我們就需要指針變量。指針變量的值就是內存中的一塊地址編號。
比如:“卑鄙是卑鄙者的通行證,高尚是高尚的墓志銘”,想把它寫入程序中,程序一啟動,這句話是要加載到內存(假設內存地址ox123456),在程序中把這句詩賦值給變量A
,把內存地址賦值給變量B
。這時候變量B
就是一個指針變量。通過變量A
和B
都能找到這句詩。
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
}
指針取值
在對普通變量使用&操作符取地址后獲得這個變量的指針,然后可以對指針使用*操作。也就是指針取值。
func main(){
// 指針取值
a := 10
b := &a // 取變量a的地址,將指針保存到b
fmt.Printf("type of b:%T\n",b) // type of b:*int
c := *b // 指針取值(根據指針去內存取值)
fmt.Printf("type of c:%T\n", c) // type of c:int
fmt.Printf("value of c:%v\n", c) // value of c:10
}
總結:取地址操作符&
和取值操作符*
是一對互補操作符。
&
取出地址,*
根據地址取出地址指向的值
變量、指針地址、指針變量、取地址、取值的相互關系和特性如下:
- 對變量進行取地址(&)操作,可以獲取這個變量的指針變量
- 指針變量的值是指針地址
- 對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值。
指針傳值示例
func modify1(x int){
x = 100
}
func modify2(x *int){
*x = 100
}
func main(){
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}
new和make
在Go語言中對於引用類型的變量,在使用時不僅要聲明它,還要為它分配內存空間,否則我們的值沒辦法進行存儲,而對於值類型的聲明,不需要分配內存空間,是因為它們在聲明的時候已經默認分配好了內存空間。
func main(){
var a *int
*a = 100
fmt.Println(*a) // 引發panic
var b map["string"]int
b["age"] = 10
fmt.Println(b) // 引發panic
}
new
new是一個內置函數,函數簽名如下:
func new(Type) *Type
// Type 表示類型,new函數只接受一個參數,這個參數是一個類型
// *Type 表示類型指針,new函數返回一個指向該類型內存地址的指針
new函數不太常用,使用new函數得到的是一個類型的指針,並且該指針對應的值為該類型的零值。
func main(){
a := new(int)
b := new(bool)
fmt.Prtintf("%T\n",a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Printf(*a) // 0
fmt.Printf(*b) //false
}
剛開始我們的實例代碼中var a *int
只是聲明一個指針變量a,但是並沒有初始化,指針座位引用類型,需要初始化后才能擁有內存空間,才可以給它進行復制。
func main(){
var a *int // 聲明a為int類型的指針
a = new(int) // 分配內存空間
*a = 10
fmt.Println(*a)
}
make
make也是用來分配內存的,區別於new,它只用於slice,map以及chan的內存創建。而且返回的類型就是這三個類型的本身。而不是它們的指針類型。因為這三種類型本身就是引用類型。所以沒必要返回它們的指針類型。
func make(t Type, size ...IntegerType) Type
make函數是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然后才可以對它們進行操作。
剛開始的示例中var b map[string]int
只是生命變量b是一個map類型的變量。不能直接進行給它復制,需要使用make函數進行操作之后,才能對其進行鍵值對賦值。
func main(){
var b map[string]int
b = make(map[string]int,10)
b["age"] = 10
fmt.Println(b)
}
new和make的區別
兩者都是用來做內存分配的。
make只用於slice、map、以及channel的初始化,返回的還是這三個引用類型本身
new用於類型的內存分配,並且內存對應的值為類型零值,返回的是指向類型的指針。