本文以面向?qū)ο鬄榛A(chǔ)搬男,闡述一下go中的某些功能的使用拣展。
高級(jí)數(shù)據(jù)類型
高級(jí)數(shù)據(jù)的關(guān)鍵字是type
。下面簡(jiǎn)單描述每種對(duì)象的聲明:
數(shù)組
數(shù)據(jù)必須定義類型缔逛,所有元素都有默認(rèn)值备埃。
//第一種 先定義姓惑,而后賦值 []中的長(zhǎng)度不可以省略,運(yùn)行會(huì)出錯(cuò)
var arr [2]int
arr01 := []int{}
arr[0] = 2
//第二種
arr1 := [3]int{1, 2, 3}
//第三種 此處的[]中可以寫長(zhǎng)度值按脚,也可以不寫于毙,也可以寫...
//因?yàn)楹竺娴膡}里有值,go會(huì)自動(dòng)計(jì)算出該數(shù)組的長(zhǎng)度
arr2 := []int{4, 5, 6}
arr21 := [...]int{4, 5, 6}
//第四種 不按順序來編寫數(shù)組的值辅搬,而是按照索引
//下面數(shù)據(jù)的輸出是[3 1]
//索引1位置的是1唯沮,索引0位置的是3
arr3 := []int{1: 1, 0: 3}
fmt.Println(arr, arr01, arr1, arr2, arr21, arr3)
基本方法:
- len():求數(shù)組長(zhǎng)度,如len(arr21)堪遂。
- cap():求數(shù)組容量介蛉,如cap(arr21)。
- append():給數(shù)組追加元素(不會(huì)覆蓋默認(rèn)值)溶褪。arr21=append(arr21,22,23)币旧,值為[5 6 22 23]
Tips:關(guān)于len()和cap()的區(qū)別
在數(shù)組中,這兩個(gè)是一樣的猿妈。
在切片中吹菱,cap()是總?cè)萘浚琹en()是可見元素的長(zhǎng)度彭则。所以cap()>=len()鳍刷。
切片
切片,數(shù)組的一種贰剥,類似于java中的數(shù)組截?cái)嗲憬恕5莏ava中的截?cái)嗍巧梢粋€(gè)新數(shù)組筷频,這個(gè)是返回指定索引蚌成,還有可能通過切片擴(kuò)容訪問原數(shù)組。
數(shù)組中的所有方法都可以給切片使用凛捏。
var numbers4 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice5 := numbers4[4:6:8]
fmt.Println(len(slice5), cap(slice5))
fmt.Println("slice5-1:", slice5)
slice5 = slice5[:cap(slice5)]
fmt.Println("slice5-2:", slice5)
slice5 = append(slice5, 11, 12, 13)
fmt.Println("slice5-3:", slice5)
如下是輸出內(nèi)容:
2 4
slice5-1: [5 6]
slice5-2: [5 6 7 8]
slice5-3: [5 6 7 8 11 12 13]
關(guān)于切片的使用担忧,有一種比numbers4[4:6:8]
簡(jiǎn)單的用法,numbers4[4:6]
坯癣。不同的是前者擴(kuò)容瓶盛,會(huì)找原數(shù)組下標(biāo)為8的元素,并將原數(shù)組下標(biāo)48的元素返回示罗,而后者一直都是原數(shù)組下標(biāo)46的元素惩猫。
字典
類似于在java中的map。其關(guān)鍵字也是map蚜点。
示例如下:
//定義一個(gè)string類型的鍵轧房,int類型值的字典(不初始化則{}內(nèi)為空即可)
enums := map[string]int{"Z": 100, "W": 44}
//給enum添加鍵值對(duì),鍵為“X”绍绘,值為99
enums["X"] = 99
//獲取鍵為“Z”的值
i := enums["Z"]
fmt.Println(enums, i)
//獲取鍵為“X”的值奶镶,如果沒有迟赃,返回該類型的默認(rèn)值
//如果沒有,則此時(shí)ok為false
p, ok := enums["X"]
fmt.Println(enums, p, ok)
//刪除鍵值為“Z”的鍵值對(duì)
delete(enums, "Z")
fmt.Println(enums)
輸出結(jié)果:
map[W:44 X:99 Z:100] 100
map[W:44 X:99 Z:100] 99 true
map[W:44 X:99]
關(guān)于p, ok := enums["X"]
這種寫法厂镇,以后會(huì)很常見這種寫法纤壁。因?yàn)閷?duì)獲取的元素是否存在的不確定性,所以ok變量是表示該元素是否存在的bool值捺信。
通道
通道類型為chan酌媒,用符號(hào)<-
來表示。其數(shù)據(jù)格式類似于隊(duì)列迄靠。示例如下:
//表示簡(jiǎn)歷4個(gè)長(zhǎng)度的通道
ints := make(chan int, 4)
ints <- 1
ints <- 2
i := <-ints //此處是1
ints <- 4
ints <- 7
ints <- 71
<-ints //此處是2
fmt.Println(i, <-ints) //此處是4
關(guān)于上面的通道ints馍佑,直接輸出ints位地址,而<-ints表示ints中存儲(chǔ)的值梨水。而<-ints也可以和別的變量進(jìn)行運(yùn)算拭荤。
在i:=<-ints
這一行,會(huì)彈出一個(gè)長(zhǎng)度的數(shù)據(jù)疫诽。當(dāng)塞入的長(zhǎng)度超出指定的長(zhǎng)度時(shí)舅世,會(huì)拋出錯(cuò)誤。相當(dāng)于隊(duì)列滿了奇徒,不能再塞入了雏亚。
fatal error: all goroutines are asleep - deadlock!
goroutines是go的函數(shù)模型,可以理解為一次性輕量級(jí)線程摩钙。在不使用協(xié)程時(shí)罢低,主函數(shù)運(yùn)行完了,此時(shí)沒有其他函數(shù)去執(zhí)行管道設(shè)值胖笛,程序就放棄等待了网持,然后出錯(cuò)了。
針對(duì)于上面的情況长踊,
i1 := 1
//定義int型通道c1功舀,默認(rèn)長(zhǎng)度為0
c1 := make(chan int)
go func() {
c1 <- 11
}()
fmt.Println("第一次:", i1)
//c1中的值賦值給i1
i1 = <-c1
fmt.Println("第二次:", i1)
輸出的結(jié)果:
第一次: 1
第二次: 11
因?yàn)槟J(rèn)的通道為非緩沖通道,其長(zhǎng)度為0身弊,所以此時(shí)需要借助協(xié)程來操作辟汰。而第一個(gè)例子的通道指定了長(zhǎng)度,叫做緩沖通道阱佛。
而go fun(){}
表示一個(gè)協(xié)程來運(yùn)行其所包含的代碼帖汞。
協(xié)程
理解協(xié)程,要和多線程進(jìn)行比較凑术。二者的共同點(diǎn)就是并發(fā)翩蘸,但是協(xié)程是線程自己內(nèi)部進(jìn)行切換,由程序決定麦萤,而多線程是多個(gè)線程間切換鹿鳖,有切換上下文開銷扁眯。相比較而言,協(xié)程開銷小的多翅帜。
函數(shù)
在go中方法首字母大寫等于public
(公開)姻檀,小寫等于protect
(同包)。沒有其他訪問范圍限制涝滴。
對(duì)于JS中函數(shù)熟悉的人绣版,對(duì)go中的函數(shù)理解也會(huì)很快的。
函數(shù)的基本形式為:
//第一種形式
func sum(a int, b int) int {
return a+b
}
//第二種形式
func sum1(a int,b int)(c int) {
c=a+b
return
}
對(duì)于函數(shù)歼疮,入?yún)樽兞棵?變量類型杂抽。輸出類型在最后。void類型的不寫韩脏。如果入?yún)⒌念愋筒糠窒嗤豸铮瑒t可以sum(a, b int)
這樣寫法。在go中赡矢,所有方法不管出入?yún)?shù)類型是否相同杭朱,方法名必須不同!
在JS中吹散,函數(shù)可以用匿名變量代替弧械,針對(duì)于上面的sum函數(shù):
//第一種
var s0 = sum
s0(1, 2)
//第二種
var s1 = func(a, b int) int {
return a + b
}
s1(2, 3)
對(duì)于匿名函數(shù),還有一種是在第二種后面直接加()空民,寫上入?yún)⑷刑疲@種是一次性的,個(gè)人感覺有點(diǎn)類似于閉包界轩。復(fù)用性不大画饥,有興趣的可以自己嘗試。
go中的函數(shù)支持返回多個(gè)值耸棒,如下示例:
func sum2(a, b int) (int, bool) {
return a + b, true
}
結(jié)構(gòu)體
go中的結(jié)構(gòu)體類似于就java中的類荒澡。他可以封裝屬性报辱、函數(shù)等与殃。
示例,演示go中的繼承:
type Animal struct {
name string
Age int
}
type Dog struct {
//繼承部分
Animal
color string
name string
}
func main() {
animal := Animal{"hh", 33}
var dog Dog
dog.Animal.name="XX1"
dog.name="XX2"
fmt.Println(animal, dog)
}
輸出如下:
{hh 33} {{XX1 0} XX2}
值得一提的是碍现,在Dog類中幅疼,如果沒有name屬性,那么調(diào)用dog.name
屬性會(huì)直接指向dog.Animal.name
屬性昼接。
匿名結(jié)構(gòu)體
dog := struct {
name string
age int
color string
}{"小黃", 5, "#FF0000"}
fmt.Println(dog)
方法體
方法體爽篷,即屬于某一結(jié)構(gòu)專屬的方法調(diào)用。
方法體是一種特殊的函數(shù)慢睡。所以上面函數(shù)部分的規(guī)則也同樣適用于方法體逐工。
func (animal *Animal) GrowUp() {
animal.Age++
}
接口
一個(gè)接口代表著某一種類型的行為铡溪。
接口的基本定義格式如下:
type Animal interface {
Grow()
Move(string) string
}
可以發(fā)現(xiàn),接口內(nèi)方法的定義和java是有相似之處的——都只有方法聲明泪喊,沒有方法體棕硫。在go的接口中,不支持屬性袒啼。
接口的類型轉(zhuǎn)換
func main() {
d := Dog{"Robert", 3, "Beijing"}
v := interface{}(&d)
animal := v.(Animal)
fmt.Println(animal)
}
type Animal interface {
Grow()
Move(string) string
}
type Dog struct {
name string
Age int
address string
}
func (dog *Dog) Grow() {
dog.Age++
}
func (dog *Dog) Move(address string) string {
oldAddr := dog.address
dog.address = address
return oldAddr
}
上面的&d
表示取地址操作哈扮。在下面會(huì)描寫這部分的。而interface{}(&d)
返回的類型用 reflect.TypeOf 查看是*main.Dog
蚓再。需要說一下的是interface{}
是空接口滑肉,它相當(dāng)于java中的Object。go中的任何類型都是它的實(shí)現(xiàn)類型摘仅。再緊隨其后的v.(Animal)
是強(qiáng)制Dog類型轉(zhuǎn)換為Animal類靶庙。
指針
對(duì)于java小伙伴來說,指針娃属、地址等都只是概念性了解惶洲,不像C++中,直接操作地址膳犹、指針等恬吕。而C/C++的性能之所以高,指針功不可沒须床。但是在C/C++中的回收控制不太好铐料。所以下面從使用方面來了解一下指針與地址。
操作指針設(shè)計(jì)兩個(gè)字符——&
和*
豺旬。簡(jiǎn)單的來說钠惩,&是取地址的,*是取值的族阅。
基本類型
house := "wahaha"
// 對(duì)字符串取地址, ptr類型為*string
ptr := &house
// 打印ptr的類型篓跛,地址
fmt.Println(reflect.TypeOf(ptr), ptr)
//對(duì)ptr取值操作
fmt.Println(*ptr)
此時(shí)輸出結(jié)果為
*string 0xc00004c1c0
wahaha
如想再深入了解,可以參見我整理的C++中的指針坦刀。
對(duì)象類型
還是以上面的Dog類的Grow方法愧沟。
func (dog *Dog) Grow() {
fmt.Println(&dog)
dog.Age++
}
此時(shí)打印輸出的結(jié)果是 0xc000006028。
我們注意到了所有的方法中鲤遥,前面的都是*Dog
沐寺,那么此時(shí)*Dog和Dog有什么區(qū)別呢?
此時(shí)的*Dog是Dog類的一個(gè)指針類型盖奈,簡(jiǎn)單點(diǎn)說混坞,Dog類型的對(duì)象在程序中有很多,怎么確定是你這個(gè)d來調(diào)用方法呢?就要用到指針類型究孕。上面的方法也叫指針方法啥酱。而如果是:
func (dog Dog) Grow() {
dog.Age++
}
此時(shí)就會(huì)將你的d復(fù)制一份,而復(fù)制的這一份與原來的d只有數(shù)值的相同而已厨诸,兩者不共用地址懈涛,所以在func (dog Dog) Grow()
方法中,計(jì)算完后泳猬,函數(shù)的入?yún)og就沒有再用了批钠,也就是此時(shí)復(fù)制的這一份沒有再用了,就會(huì)進(jìn)行銷毀得封,而且這個(gè)復(fù)制對(duì)象的生存與否與d沒有任何關(guān)系埋心,也不會(huì)影響d的數(shù)值變化。這個(gè)方法也叫值方法忙上。