3. Go 數(shù)據(jù)類型及數(shù)據(jù)結(jié)構(gòu)
前面的幾節(jié)中有意無意地創(chuàng)建了很多變量,在變量聲明過程中,除非聲明過程就初始化硕旗,否則通常需要指明數(shù)據(jù)類型。不同的數(shù)據(jù)類型代表著不同的數(shù)據(jù)處理和表達(dá)方法女责,為了滿足對(duì)現(xiàn)實(shí)世界的刻畫要求漆枚,通常需要?jiǎng)?chuàng)建復(fù)雜的數(shù)據(jù)類型或結(jié)構(gòu),因?yàn)橐粋€(gè)好的數(shù)據(jù)類型或結(jié)構(gòu)可以恰到好處的解決所面臨的問題抵知,所以需要考慮數(shù)據(jù)結(jié)構(gòu)的問題墙基,但無論多么復(fù)雜的數(shù)據(jù)類型或結(jié)構(gòu),都是由簡單的數(shù)據(jù)類型構(gòu)建而來的刷喜,下面逐一進(jìn)行熟悉残制。
Go 簡單數(shù)據(jù)類型
Go 中簡單數(shù)據(jù)類型大概有這么幾類:
- 布爾型:
bool
,表示true
和false
- 數(shù)值型:
complex64
吱肌,complex128
痘拆,float32
,float64
氮墨,int
纺蛆,int8
,int16
规揪,int32
桥氏,int64
,int128
猛铅,unit
字支,unit8
,uint16
,uint32
堕伪,uint128
揖庄,表示各種大小長度的數(shù)字 - 字符串類型:
string
,表示字符串 - 指針:Pointer
- 雜項(xiàng):
byte, rune
布爾型
布爾型欠雌,只有兩種值蹄梢,true
和 false
,但確實(shí)一種重要的表示狀態(tài)的存在富俄,布爾型變量主要進(jìn)行邏輯運(yùn)算禁炒,生成布爾型值的方式除了直接聲明外,多數(shù)都是通過關(guān)系運(yùn)算符獲取的霍比。
關(guān)系運(yùn)算符幕袱,關(guān)系運(yùn)算符用于可比較的兩種類型的值進(jìn)行比較,包括悠瞬, ==, !=, > , >=, <, <=
-
==
檢測(cè)兩個(gè)值是否相等们豌,相等返回 true,否則返回false
-
!=
檢測(cè)兩個(gè)值是否不相等阁危,相等返回false
玛痊,否則返回true
-
>, >=
比較左端和右端兩個(gè)值的大小,左端大于/大于等于右端的值狂打,返回true
擂煞,否則返回false
-
<, <=
比較左端和右端兩個(gè)值的大小,左端小于/小于等于右端的值趴乡,返回true
对省,否則返回false
邏輯運(yùn)行,邏輯運(yùn)算包括三種晾捏,與或非蒿涎,對(duì)應(yīng)符號(hào)為 &&, ||, !
兩個(gè)/一個(gè)布爾型變量做上述運(yùn)行時(shí),與數(shù)學(xué)上的真值表是一致的惦辛。
-
x && y
結(jié)果要為true劳秋,要求x 為 true 并且 y 為 true
,其他均為false -
x || y
結(jié)果要為true胖齐,要求x 為 true 或者 y 為 true
玻淑,其他均為false -
!x
x為true
,!x
就是false
呀伙; 反之补履,x為false,!x
就是true
邏輯運(yùn)算的優(yōu)先順序是 非剿另,與箫锤,或贬蛙,但是為了自己邏輯清楚,可以加小括號(hào)表示哪些關(guān)系密不可分谚攒。
注意:Go 中阳准,true,false
并不能和 1, 0
進(jìn)行默認(rèn)轉(zhuǎn)換五鲫,必須顯式的進(jìn)行轉(zhuǎn)換溺职。
func i2b(i int) bool {
if i > 0 {
return 1
}
return 0
}// int to bool
func b2i(b bool) int {
if b {
return 1
}
return 0
}// bool to int
數(shù)值型
Go 中數(shù)值型有兩種岔擂,一種是沒有任何長度標(biāo)記的 int, uint
這兩種位喂,另一種就是有長度標(biāo)記的數(shù)值,例如 int8, uint16
乱灵。前者的數(shù)據(jù)長度與平臺(tái)相關(guān)塑崖,后者與平臺(tái)無關(guān),個(gè)人感覺是痛倚,如果你不排斥使用后者规婆,那么你最好使用后者。
對(duì)于整型數(shù)據(jù)來說蝉稳, uintx
的為無符號(hào)整型抒蚜, intx
的為有符號(hào)整型,兩者可以表達(dá)的數(shù)據(jù)范圍是不一致的耘戚。
- uint8: 無符號(hào) 8 位整型 (0 到 255)
- uint16: 無符號(hào) 16 位整型 (0 到 65535)
- uint32: 無符號(hào) 32 位整型 (0 到 4294967295)
- uint64: 無符號(hào) 64 位整型 (0 到 18446744073709551615)
- int8: 有符號(hào) 8 位整型 (-128 到 127)
- int16 有符號(hào) 16 位整型 (-32768 到 32767)
- int32: 有符號(hào) 32 位整型 (-2147483648 到 2147483647)
- int64: 有符號(hào) 64 位整型 (-9223372036854775808 到 9223372036854775807)
- float32:32 位浮點(diǎn)數(shù) (很大)
- float64:64 位浮點(diǎn)數(shù) (非常大)
- complex64:實(shí)部和虛部都是 float32 類型的的復(fù)數(shù)
- complex128:實(shí)部和虛部都是 float64 類型的的復(fù)數(shù)
對(duì)于數(shù)值型變量嗡髓,最為常見的就是進(jìn)行算術(shù)運(yùn)算符,包括 +, -, *, /, %, ++, --, +=, *=, /=
-
%
整數(shù)取余收津,與被取模數(shù)的符號(hào)是一致的; -
x++, x--
自增和自減(沒有++x,--x這樣的東東)晌缘; -
x+=y
就是x = x+y
的簡寫運(yùn)算晦闰,其他幾個(gè)類似;
除此之外吻贿, Go 還支持位運(yùn)算
& 位運(yùn)算 AND
| 位運(yùn)算 OR
^ 位運(yùn)算 XOR
&^ 位清空 (AND NOT)
<< 左移
>> 右移
字符串
字符串就是字符的集合串结,更準(zhǔn)確一點(diǎn),是一個(gè)一旦創(chuàng)建完就不可改變的字節(jié)序列舅列。之前提到肌割,Go 語言字符串使用 UTF-8 編碼實(shí)現(xiàn),此處展開 UTF-8 不太合適剧蹂,但需要知道 UTF-8 是一個(gè)變長編碼声功,可以容納世界上絕大多數(shù)語言及其字符。
字符串長度可以使用 len
來測(cè)量宠叼,字符串支持切片訪問 string[index]
先巴,字符串支持通過 +
對(duì)兩個(gè)或多個(gè)字符串進(jìn)行拼接其爵。可以通過循環(huán)對(duì)一個(gè)字符串進(jìn)行遍歷伸蚯。
package main
import "fmt"
func main() {
b := "Hello 中國"
fmt.Printf(b+", And have %d length.\n", len(b))
fmt.Println(b[0:10])
printStringHex(b)
printStringOnebyOne(b)
}
func printStringOnebyOne(s string) {
for index, rune := range s {
fmt.Printf("%c\t%x\t%d\n", rune, rune, index)
}
}
func printStringHex(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%d\t%x\t%c\n", i, s[i], s[i])
}
}
/*
Hello 中國, And have 12 length.
Hello 中?
0 48 H
1 65 e
2 6c l
3 6c l
4 6f o
5 20
6 e4 ?
7 b8 ?
8 ad -
9 e5 ?
10 9b ?
11 bd ?
H 48 0
e 65 1
l 6c 2
l 6c 3
o 6f 4
20 5
中 4e2d 6
國 56fd 9
*/
注意對(duì)字符串遍歷的方法摩渺,因?yàn)?UTF-8 變長編碼的緣故,當(dāng)處理英文字符剂邮,編碼位置只有一個(gè)byte時(shí)摇幻,可以正常顯示,但是對(duì)于中文挥萌,無法通過位置索引得到完整的一個(gè)字符绰姻,較好的穩(wěn)妥的辦法使用 range
來遍歷,獲取 rune引瀑。你也可能發(fā)現(xiàn)了狂芋,rune
是一個(gè)數(shù)據(jù)類型,它其實(shí)是 type rune int32, 是一個(gè)足夠容納編碼位置的表達(dá)類型憨栽,按照如下方式訪問字符串也是完全可以的帜矾。
func printChars(s string) {
runes := []rune(s)
for i:= 0; i < len(runes); i++ {
fmt.Printf("%c ",runes[i])
}
}
有一些回車符,Tab符號(hào)等不可見字符的表達(dá)需要使用轉(zhuǎn)義字符屑柔,這一點(diǎn)同 C 語言一致屡萤。
\a 響鈴
\b 退格
\f 換頁
\n 換行
\r 回車
\t 制表符
\v 垂直制表符
\' 單引號(hào) (只用在 '\'' 形式的rune符號(hào)面值中)
\" 雙引號(hào) (只用在 "..." 形式的字符串面值中)
\\ 反斜杠
為了處理大段文本(里面有很多換行,縮進(jìn)之類的東西)掸宛, Go 還支持一種字面字符串死陆,它也是字符串,不同的是旁涤,它不用 ""
雙引號(hào)表達(dá)翔曲,而是使用使用反引號(hào)代替雙引號(hào)。
const GoUsage = `Go is a tool for managing Go source code.
Usage:
go command [arguments]
...`
字符串是一種最為常見的數(shù)據(jù)類型劈愚,后面還會(huì)有專門的地方涉及到它瞳遍。
指針
指針是一種直接存儲(chǔ)變量的內(nèi)存地址的數(shù)據(jù)類型。一個(gè)指針對(duì)應(yīng)變量在內(nèi)存中的存儲(chǔ)位置菌羽。并不是每一個(gè)值都會(huì)有一個(gè)內(nèi)存地址掠械,但是對(duì)于每一個(gè)變量必然有對(duì)應(yīng)的內(nèi)存地址。通過指針注祖,我們可以直接讀或更新對(duì)應(yīng)變量的值猾蒂,而不需要知道該變量的名字(如果變量有名字的話)。
Go 的指針運(yùn)算
& 返回變量存儲(chǔ)地址,
// &a; 將給出變量的實(shí)際地址是晨。
* 取出指針變量值肚菠,
// *a; 是一個(gè)取出該地址的值
注意 Go 的指針不允許指針的 ++,--
運(yùn)算。 為什么要有指針呢罩缴,因?yàn)橹羔樂奖阄梅辏瑫r(shí)也是為了降低開銷层扶。Go 在處理簡單的數(shù)據(jù)類型時(shí),數(shù)據(jù)之間的轉(zhuǎn)移和傳遞是通過復(fù)制做到的烙荷,就是你把一個(gè)數(shù)據(jù)傳遞給另一個(gè)函數(shù)镜会,那么 Go 會(huì)幫你復(fù)制一份數(shù)據(jù)過去,當(dāng)遇到該數(shù)據(jù)特別大的時(shí)候终抽,又要頻繁傳遞的時(shí)候戳表,就要進(jìn)行大量的復(fù)制,影響性能昼伴。但用指針就不存在該問題匾旭,因?yàn)橹羔樦槐4嬷赶蚰硞€(gè)數(shù)據(jù)的地址,如果需要亩码,找那個(gè)地址的數(shù)據(jù)直接去改季率,不要反復(fù)復(fù)制了。
func main() {
var oneint int
var oneptr = &oneint
var anoptr *int = new(int) //*
fmt.Println(oneint)
oneint++
fmt.Println(*oneptr)
*oneptr += 2
fmt.Println(oneint)
fmt.Println(anoptr)
fmt.Println(*anoptr)
*anoptr = oneint
fmt.Println(*anoptr)
}
/* Result:
0 oneint 的默認(rèn)值
1 oneint++
3 *oneptr += 2
0xc00000a090 //anoptr 地址
0 //*anoptr 的默認(rèn)值
3 // *anoptr
*/
上面*標(biāo)處描沟,可利用自動(dòng)推斷為 var anoptr = new(int)
,此處利用new
分配內(nèi)存空間鞭光,否則第四行輸出為nil
吏廉,因?yàn)樗兄羔樀哪J(rèn)值為nil
,就是空惰许,沒有指向任何地址席覆。對(duì) nil
地址操作 *anoptr = oneint
將引發(fā)panic,這是指針使用的大忌汹买。
Go 類型之間轉(zhuǎn)換
Go 有著非常嚴(yán)格的強(qiáng)類型特征佩伤。Go 沒有自動(dòng)類型提升或類型轉(zhuǎn)換。
package main
import (
"fmt"
)
func main() {
i := 55 //int
j := 67.8 //float64
sum := i + j //不允許 int + float64
fmt.Println(sum)
}
那該怎么辦……顯式轉(zhuǎn)換晦毙,全部需要顯式轉(zhuǎn)換
package main
import (
"fmt"
)
func main() {
i := 55 //int
j := 67.8 //float64
sum := i + int(j) //轉(zhuǎn)換j為整數(shù)型生巡,當(dāng)然精度就沒有了
fmt.Println(sum)
}
// 或者
func main() {
i := 55 //int
j := 67.8 //float64
sum :=j +float64(i) // 轉(zhuǎn)換I到浮點(diǎn)數(shù)
fmt.Println(sum)
}
把一種類型轉(zhuǎn)換為另一種類型的方法就是 T(v)
, T為新的類型,例如float64(i)
见妒。
Go 符合數(shù)據(jù)類型
數(shù)組
數(shù)組是一個(gè)由固定長度的特定類型元素組成的序列孤荣,一個(gè)數(shù)組可以由零個(gè)或多個(gè)元素組成。
數(shù)組的聲明:
var arrayname [n]type // n為確定的數(shù)字须揣,type為簡單的數(shù)據(jù)類型
var stringarray [3]string = [3]string{"Are", "Good", "Yor"}
// 最為繁重的聲明方式盐股,可以簡化為 var stringarray = [3]string{"Are", "Good", "Yor"}
for index, value := range stringarray {
fmt.Printf("%d\t%s\n", index, value)
}
// range函數(shù)會(huì)遍歷stringarray,返回兩個(gè)值耻卡,第一個(gè)是索引疯汁,第二個(gè)是數(shù)值
/* Result
0 Are
1 Good
2 Yor
*/
var uintarray [5]uint
// 只聲明,不初始化
uintarray[0] = 10
uintarray[4] = 100
for i, v := range uintarray {
fmt.Printf("%d\t%d\n", i, v)
}
/*Result
0 10
1 0
2 0
3 0
4 100
*/ 沒有賦值的位置都是 0
floatarray := [...]float32{1:2.17,5: 3.14}
// 包括個(gè)數(shù)也自動(dòng)推導(dǎo)
for _, v := range floatarray {
fmt.Printf("%g\n", v)
}
// 如果對(duì)索引位置不感興趣卵酪,就用下劃線去代替一個(gè)有意義的名稱幌蚊,這樣就會(huì)丟棄掉那個(gè)值
/* Result
0 0
1 2.17
2 0
3 0
4 0
5 3.14
*/
數(shù)組的長度是不變的秸谢,在聲明就已經(jīng)指定,而且 Go 認(rèn)為 [3]int, [4]int
是兩種不同的數(shù)據(jù)類型霹肝,無法比較估蹄。初始化過程中,可以只初始化想初始化的位置沫换,用{index:value}
來指定臭蚁,如果{}
給定的值數(shù)量小于[]
指定的量,則認(rèn)為初始化前面的幾個(gè)數(shù)讯赏,后面為零垮兑。數(shù)組長度可以使用 len(array)
來獲得,訪問數(shù)據(jù)內(nèi)的元素漱挎,可以通過 array[index]
系枪,即數(shù)組名[索引位置]來訪問,索引位置可以是單個(gè)數(shù)字磕谅,也可以是[start:end] 私爷。數(shù)組是值類型,就是傳遞數(shù)組是通過傳遞數(shù)組的副本做到的膊夹。數(shù)組的遍歷使用 for 下標(biāo)索引或 range 索引都可以衬浑。
這里需要說一下下標(biāo)的使用方法,一般來說 array[index]
是訪問單元素放刨, array[start:end]
是訪問數(shù)組從 start
開始到 end-1
的那個(gè)元素工秩,如果 start
不寫,默認(rèn)從 0 開始进统,如果 end
不寫助币,默認(rèn)到 len()-1
的位置,如果都不寫螟碎,那就是 0:len()-1
也就是全部元素眉菱。
其實(shí)還可以創(chuàng)建多維數(shù)組,聲明方法為 var mularray [row][vol]Type
來實(shí)現(xiàn)抚芦,遍歷方式就是嵌套循環(huán)range倍谜,此處不再展開。
func print2darray(a [m][n]string) {
for row, rv := range a {
for vol, rvv := range rv {
fmt.Printf("%d\t%d\t%s\n",row,vol,rvv)
}
fmt.Printf("\n")
}
}
切片/Slice
Slice(切片)代表變長的序列叉抡,序列中每個(gè)元素都有相同的類型尔崔。一個(gè)切片類型一般寫作[]T,其中T代表切片中元素的類型褥民;切片的語法和數(shù)組很像季春,只是沒有固定長度而已。切片是由數(shù)組建立的一種方便消返、靈活且功能強(qiáng)大的包裝(Wrapper)载弄。切片本身不擁有任何數(shù)據(jù)耘拇。它們只是對(duì)現(xiàn)有數(shù)組的引用。一個(gè)切片有三個(gè)要素要關(guān)心宇攻,切片頭(指針)惫叛,長度(len),容量(cap)逞刷。
切片的創(chuàng)建有兩種方式
- 從某個(gè)數(shù)組創(chuàng)建嘉涌,
s := array[start:end] 0<= start < end <= len(array)-1
- 不關(guān)心數(shù)組,
s := make([]Type, len, cap) or s:=[]int{v1,v2,...,vn}
夸浅,也不是不想關(guān)心仑最,是關(guān)心不到,反正能正常遍歷各個(gè)值帆喇,為什么要關(guān)心數(shù)組呢警医。
先說第二種方式:
因?yàn)闆]有對(duì)照數(shù)組,單純的說切片特性優(yōu)良坯钦。當(dāng)你創(chuàng)建一個(gè)切片后预皇,你依然可以像數(shù)組一樣,使用下標(biāo)訪問每個(gè)元素葫笼,使用 range 遍歷整個(gè)切片深啤。
既然說了可以變長,那就一定可以添加元素路星,使用 append 函數(shù)追加元素,格式為s = append(s, v1, v2, ...) or s = append(s, s1...)
就是說 append 函數(shù)可以在一個(gè)切片后面追加同類型的元素诱桂,或一個(gè)同類型的切片洋丐。當(dāng)append
元素的數(shù)量超過原有切片的容量時(shí),Go 會(huì)自動(dòng)擴(kuò)大容量挥等,擴(kuò)容多少友绝,由一套復(fù)雜的算法決定,可不用關(guān)心肝劲。
從一個(gè)切片上迁客,還可以繼續(xù)創(chuàng)建子切片,subs := s[start:end]0<= start < end <= len(array)-1
辞槐,使用和切片相同掷漱。
理解第二種,就要回來說第一種了榄檬,有點(diǎn)復(fù)雜卜范,看例子說明。
var uintarray [10]uint
uintarray[0] = 0
uintarray[2] = 100
uintarray[4] = 200
uintarray[6] = 300
uintarray[8] = 400
fmt.Println(uintarray, len(uintarray)) // [0 0 100 0 200 0 300 0 400 0] 10
uints := uintarray[1:3]
fmt.Println(uints, len(uints), cap(uints)) //[0 100] 2 9
fmt.Println(uints[0:3], len(uints[0:3]), cap(uints[0:3])) // [0 100 0] 3 9
fmt.Println(uints[1:4], len(uints[1:4]), cap(uints[1:4])) // [100 0 200] 3 8
uints[1] = 250
fmt.Println(uints[1:4], len(uints), cap(uints)) // [250 0 200] 3 8
uints[2] = 350 // panic鹿榜,index out of range
uints = append(uints, 600, 700)
fmt.Println(uints, len(uints), cap(uints)) // [0 250 600 700] 4 9
fmt.Println(uintarray, len(uintarray)) // [0 0 250 600 700 0 300 0 400 0] 10
// 切片是對(duì)數(shù)組的引用海雪,所以通過切片可以修改數(shù)組的值
uints = append(uints, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400)
fmt.Println(uints, len(uints), cap(uints))
// [0 250 600 700 600 700 800 900 1000 1100 1200 1300 1400] 13 18
fmt.Println(uintarray, len(uintarray))
//[0 0 250 600 700 0 300 0 400 0] 10
// 你不是說锦爵,修改切片,數(shù)組就相應(yīng)變了嗎奥裸!怎么這次沒有變险掀? 因?yàn)椤?因?yàn)檫@次添加的數(shù)太多了,超過切片容量湾宙,Go 重新創(chuàng)建了一個(gè)底層數(shù)組樟氢,復(fù)制了原有切片位置的值,添加新的值后返回了新的底層數(shù)組创倔。而原有的數(shù)組被拋棄了嗡害,但我們有一個(gè)可以訪問原有數(shù)組的變量存在,所以發(fā)現(xiàn)數(shù)沒有變化畦攘。
ints := make([]int, 3, 3)
fmt.Println(ints, len(ints), cap(ints)) // [0 0 0] 3 3
aints := []int{1, 2, 3}
fmt.Println(aints, len(aints), cap(aints)) // [1 2 3] 3 3
sumints := append(ints, aints...)
fmt.Println(sumints, len(sumints), cap(sumints)) // [0 0 0 1 2 3] 6 6
subsumints := sumints[1:3]
fmt.Println(subsumints, len(subsumints), cap(subsumints)) //[0 0] 2 5
subsumints2 := sumints[:5]
fmt.Println(subsumints2, len(subsumints2), cap(subsumints2)) //[0 0 1 2 3] 5 5
// 創(chuàng)建的子切片長度比原有切片長度要大霸妹,但容量是一樣
所以,一個(gè)切片一旦創(chuàng)建知押,無論何種方式創(chuàng)見的切片叹螟,其指針位置就確定了,指針前面的數(shù)據(jù)就永遠(yuǎn)不能訪問了台盯;在切片容量范圍內(nèi)罢绽,可以創(chuàng)建子切片,訪問到由于容量所有數(shù)據(jù)静盅;對(duì)于append 可以增加切片容量良价。切片對(duì)于底層數(shù)組是引用方式,修改切片就修改了底層數(shù)組蒿叠,切片還有一種操作叫做 復(fù)制(copy)明垢,copy(dst, source)
從 source 復(fù)制數(shù)據(jù)到一個(gè) dst 切片,如果想完整復(fù)制 source市咽,則必須保證 dst 切片容量大于等于 source痊银,否則只會(huì)復(fù)制dst容量的數(shù)據(jù)。
Map
Map有多種譯名:字典施绎,哈希表溯革,集合......,其實(shí)都可以谷醉,因?yàn)?Map 是這樣一種結(jié)構(gòu)致稀,非空的Map 里面是一系列鍵值對(duì)(鍵到值的映射),類似于一本字典孤紧;它的鍵是不允許重復(fù)的豺裆,所以它像一個(gè)集合;哈希表是 Map 的實(shí)現(xiàn)方式,它用哈希算法計(jì)算存儲(chǔ)鍵臭猜,使得它可以對(duì)給定的key可以在常數(shù)時(shí)間復(fù)雜度內(nèi)檢索躺酒、更新或刪除對(duì)應(yīng)的value。由于不同的哈希算法生成的排序方式不同蔑歌,所以Map是無序羹应,更為特殊的,在 Go 中次屠,它是故意被設(shè)計(jì)為亂序的园匹,要求程序算法不能依賴于Map遍歷的順序。如果像排序劫灶,最好顯式的獲取Map的鍵裸违,按照需要方式排序,然后再按照鍵去遍歷本昏。Map 是按照引用傳遞的供汛,意思就是無論你在哪里修改一個(gè)Map,其都是對(duì)一個(gè)Map進(jìn)行修改涌穆。Map 無法使用 ==
比較怔昨,除非是 MapName == nil
Map 聲明
var mapname map[Ktype]Vtype{Key:Value}
var monthTable map[int]string
創(chuàng)建了一個(gè)從數(shù)字到月份名字的Map
var monthTable map[string]int
創(chuàng)建了一個(gè)從月份名字到數(shù)字的Map
如果沒有利用{Key:Value}
初始化map, 則聲明的Map默認(rèn)值為 nil
。如果你想添加元素到 nil map 中宿稀,會(huì)觸發(fā)運(yùn)行時(shí) panic趁舀。因此 map 必須使用 make
函數(shù)初始化。
初始化 monthTable = make(map[int]string)
祝沸,所以多數(shù)時(shí)候矮烹,聲明和初始化是一起進(jìn)行的,monthTable := make(map[int]string)
Map 的查詢
Value,ok = MapName[key]
如果可以在給定的Map中查詢到指定 key 的值罩锐,那么返回 value 和 true
擂送,反之,返回值的默認(rèn)零值和 false
.
Map 鍵值的添加
MapName[key] = value
如果 Map 初始化過唯欣,那么添加就是即可,如果沒有初始化搬味,panic報(bào)錯(cuò)
Map 鍵值的刪除
delete(key, MapName)
如果Map 中存在該key境氢,則刪除,如果不存在碰纬,上述操作也是安全操作萍聊,最終結(jié)果就是一定沒有該鍵。
package main
import (
"fmt"
)
func main() {
var StudentScore map[string]int
// var StudentScore map[int]string
fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))
StudentScore = make(map[string]int)
fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))
StudentScore["Xiaoming"] = 98
StudentScore["Xiaohong"] = 99
StudentScore["Xiaogang"] = 95
StudentScore["Xiaohei"] = 0
fmt.Println("Xiaoming Score:", StudentScore["Xiaoming"])
fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))
for k, v := range StudentScore {
fmt.Println(k, ":", v)
}
fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])
fmt.Println("JackFly Score:", StudentScore["JackFly"])
lookupScore("Xiaohei", StudentScore)
lookupScore("JackFly", StudentScore)
setScore("JackFly", 100, StudentScore)
fmt.Println("JackFly Score:", StudentScore["JackFly"])
setScore("Xiaohei", 100, StudentScore)
fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])
delScore("Xiaohei", StudentScore)
setScore("Xiaohei", 99, StudentScore)
fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])
}
func lookupScore(s string, table map[string]int) {
Score, ok := table[s]
if ok {
fmt.Printf("%s Score: %d, ok Status:%t\n", s, Score, ok)
} else {
fmt.Printf("There is no %s Score in Table. ok Status:%t\n", s, ok)
}
}
func setScore(s string, score int, table map[string]int) {
if table == nil {
table = make(map[string]int)
}
Score, ok := table[s]
if ok {
fmt.Printf("%s already had a score:%d record in table.\n", s, Score)
} else {
table[s] = score
fmt.Printf("%s Score:%d had set in table successfully.\n", s, score)
}
}
func delScore(s string, table map[string]int) {
Score, ok := table[s]
if ok {
delete(table, s)
fmt.Printf("%s score:%d had been delete in table.\n", s, Score)
} else {
fmt.Printf("There is no %s score record in talbe.\n", s)
}
}
/*
map[] true 0
map[] false 0 // 初始化后就不是nil
Xiaoming Score: 98
map[Xiaohong:99 Xiaogang:95 Xiaohei:0 Xiaoming:98] false 4 // len可以給出map長度
Xiaoming : 98
Xiaohong : 99
Xiaogang : 95
Xiaohei : 0
Xiaohei Score: 0
JackFly Score: 0
Xiaohei Score: 0, ok Status:true
There is no JackFly Score in Table. ok Status:false
JackFly Score:100 had set in table successfully.
JackFly Score: 100
Xiaohei already had a score:0 record in table.
Xiaohei Score: 0
Xiaohei score:0 had been delete in table.
Xiaohei Score:99 had set in table successfully.
Xiaohei Score: 99
*/
結(jié)構(gòu)體
結(jié)構(gòu)體是一種聚合的數(shù)據(jù)類型悦析,是由零個(gè)或多個(gè)任意類型的值聚合成的新數(shù)據(jù)類型寿桨,以滿足描述算法或?qū)ο蟮氖褂妹枋鲆螅渲薪Y(jié)構(gòu)體的每個(gè)值稱為結(jié)構(gòu)體的成員。
結(jié)構(gòu)體的定義與聲明 亭螟, 結(jié)構(gòu)體內(nèi)成員的聲明順序是有意義的挡鞍,不同順序成員的結(jié)構(gòu)體是不同的結(jié)構(gòu)體。成員數(shù)據(jù)類型可以是前面的任意一種數(shù)據(jù)類型预烙,甚至可以是另一個(gè)結(jié)構(gòu)體(但不可以是自己)墨微。
// 結(jié)構(gòu)體定義
type StructName struct{
feildName1 TypeA
feildName2, feildName3 TypeB
feildName3, TypeC
} // 帶有成員名稱(字段)的定義方式
type StructName struct{
TypeA
TypeB
TypeC
} // 不帶字段的定義方式
// 結(jié)構(gòu)體聲明
StructureV := StructureName{
feildName1: Value1,
feildName2: Value2,
feildName3: Value3,// 不一定要全部賦值扁掸,如果不賦值翘县,Go 默認(rèn)初始化為0
}
StructureV := StructureName{Value1, Value2, Value3}//由此注意結(jié)構(gòu)體的聲明成員的的順序很重要,
創(chuàng)建匿名的結(jié)構(gòu)體
StructureV :=struct{
feildName1 TypeA
feildName2, feildName3 TypeB
feildName3谴分, TypeC
}{
feildName1: Value1,
feildName2: Value2,
feildName3: Value3,
}
結(jié)構(gòu)體成員的訪問
訪問結(jié)構(gòu)體的成員是通過.
來操作的锈麸,即 structV.fieldNameA
,用來讀寫都是可以的
結(jié)構(gòu)體取地址及地址訪問
結(jié)構(gòu)體可以取地址牺蹄,結(jié)構(gòu)體的成員也可以單獨(dú)取地址忘伞,通常通過地址訪問成員,需要先對(duì)結(jié)構(gòu)體使用 *
取值運(yùn)算钞馁,但 Go 會(huì)自動(dòng)對(duì)結(jié)構(gòu)體地址進(jìn)行取值 虑省,簡化了結(jié)構(gòu)體的表達(dá),如下:structptr := &structName{}, (*structptr).fieldName 等價(jià)于 structptr.feildName
結(jié)構(gòu)體內(nèi)包含另一個(gè)結(jié)構(gòu)體
structName{substructName, fnc typec}
時(shí)僧凰,如果另一個(gè)結(jié)構(gòu)體substructName{fna typea, fnb typeb}
沒有名稱探颈,那么Go 會(huì)提升字段,就是將substructName
中的成員训措,當(dāng)成自己的成員伪节,可以通過.
直接訪問。但是對(duì)于有名稱的結(jié)構(gòu)體绩鸣,則必須使用雙重點(diǎn)號(hào)訪問子結(jié)構(gòu)體的成員怀大。structName{sub substructName, fnc type}, structName.substructName.fna
結(jié)構(gòu)體雖然沒有辦法包含自身在內(nèi),但是可以包含自身的指針在內(nèi)呀闻。
package main
import (
"fmt"
)
type address struct {
city, road string
number int
Neighbor *address
}
type student struct {
Name string
gender bool // true for boy, false for girl
age int
hight float32
}
type studentDetail struct {
student
homeAddr address
}
func main() {
xiaoming := student{"xiaoming", true, 10, 1.1}
fmt.Println(xiaoming)
fmt.Printf("%+v\n", xiaoming)
printfstu(xiaoming)
xiaominginfo := &xiaoming
fmt.Println((*xiaominginfo).gender)
fmt.Println(xiaominginfo.age)
fmt.Println("A Year Later... ")
xiaominginfo.age++
xiaominginfo.hight = 1.2
printfstu(xiaoming)
xiaomingDetail := studentDetail{
student: xiaoming,
homeAddr: address{
city: "Beijing",
road: "ChangAn",
number: 1,
//Neighbor 是可以被省略的
},
}
fmt.Printf("%+v\n", xiaomingDetail)
printfstuD(&xiaomingDetail)
xiaohongDetail := &studentDetail{student{"xiaohong", false, 10, 1.1}, address{"Beijing", "Changan", 2, &xiaomingDetail.homeAddr}} // 地址不可省略
fmt.Println("xiaohong Age:", xiaohongDetail.age)
fmt.Println("xiaohong home address:", xiaohongDetail.homeAddr.road, xiaohongDetail.homeAddr.number)
fmt.Println("xiaohong Neighbor:", *xiaohongDetail.homeAddr.Neighbor)
}
func printfstu(stu student) {
gendermap := map[bool]string{
false: "girl",
true: "boy",
}
fmt.Printf("------------\n%s\nGender:%s\nAge:%d\nHight:%g\n", stu.Name, gendermap[stu.gender], stu.age, stu.hight)
}
func printfstuD(stuD *studentDetail) {
gendermap := map[bool]string{
false: "girl",
true: "boy",
}
fmt.Printf("------------\n%s\n - Gender: %s\n - Age: %d\n - Hight: %g\n - Address: %s, %s Road, No. %d\n", stuD.Name, gendermap[stuD.gender], stuD.age, stuD.hight, stuD.homeAddr.city, stuD.homeAddr.road, stuD.homeAddr.number)
}
由于這一節(jié)打印了很多不同類型的值化借,其格式化的方式都有所區(qū)別,可以通過這個(gè) 簡單介紹 簡單了解一下捡多,后文會(huì)有機(jī)會(huì)全面理解蓖康。