方法賦值給變量稱為方法表達(dá)式
age:=Age(25)
//方法賦值給變量,方法表達(dá)式
sm:=Age.String
//通過變量,要傳一個(gè)接收者進(jìn)行調(diào)用也就是age
sm(age)
方法String 其實(shí)沒有參數(shù)的他去,但是通過方法表達(dá)式賦值給變量sm后,在調(diào)用的時(shí)候倒堕,必須要傳一個(gè)接收者,這樣sm才知道怎么調(diào)用
不管方法是否有參數(shù),通過方法表達(dá)式調(diào)用涯雅,第一個(gè)參數(shù)必須是接收者,然后才是方法自身的參數(shù)
結(jié)構(gòu)體:
結(jié)構(gòu)體定義
結(jié)構(gòu)體是一種聚合類型纫事,里面可以包含任何類型的值勘畔,這些值可就是我們定義結(jié)構(gòu)體的成員,也稱為字段丽惶。在Go語言中炫七,要定義一個(gè)結(jié)構(gòu)體需要使用type+struct關(guān)鍵字組合
在下面的例子中,定義一個(gè)結(jié)構(gòu)體類型钾唬,名稱為person,表示一個(gè)人万哪。這個(gè)person結(jié)構(gòu)體有2個(gè)字段: name 代表這個(gè)人的名稱俐筋,age代表年齡
type person struct {
name string
age uint
}
在定義結(jié)構(gòu)體時(shí)娘侍,字段聲明方法和平時(shí)聲明一個(gè)變量是一樣的,都是變量名在前驶冒,類型在后儒士,只不過在結(jié)構(gòu)體中的止,變量名稱為成員名字段名
結(jié)構(gòu)體的成員名字段并不是必須的,也可以一個(gè)字段也沒有着撩,這種結(jié)構(gòu)體稱為空結(jié)構(gòu)體
根據(jù)以上信息冲杀,我們可以總結(jié)出結(jié)構(gòu)體定義的表達(dá)式
type structName struct{
fieldName typeName
....
....
}
其中:
type和struct 是go語言的關(guān)鍵字,二者組合就代表要定義一個(gè)新的結(jié)構(gòu)體類型
structName 是結(jié)構(gòu)體類型的名字
fieldName 是結(jié)構(gòu)體的字段名睹酌,而 typeName是對應(yīng)字段的;類型
字段可以是零個(gè)剩檀,一個(gè)或者多個(gè)
結(jié)構(gòu)體也是一種類型
定義好結(jié)構(gòu)體后就可以使用了憋沿,因?yàn)樗且粋€(gè)聚合類型,所以比普通類型可以攜帶更多的數(shù)據(jù)
結(jié)構(gòu)體聲明使用
結(jié)構(gòu)體類型和普通的字符串沪猴,整型一樣辐啄,也可以使用同樣的方式聲明和初始化。
在下面的例子中运嗜,聲明一個(gè)person類型的變量p,因?yàn)闆]有對變量p初始化壶辜,所以默認(rèn)會(huì)使用結(jié)構(gòu)體字段的零值。
var p person
當(dāng)然在聲明一個(gè)結(jié)構(gòu)體變量的時(shí)候担租,也可以通過結(jié)構(gòu)體字面量的方式初始化砸民,如下面的代碼所示:
p:=person{"飛雪",30}
采用簡短聲明發(fā),同時(shí)采用字面量初始化的方式,把結(jié)構(gòu)體變量p的name 初始化為"飛雪",age初始化為30岭参,以逗號(hào)分隔
聲明一個(gè)結(jié)構(gòu)體變量后就可以使用了反惕,下面運(yùn)行以下代碼,驗(yàn)證name和age的值是否和初始化的一樣:
fmt.printIn(p.name,p.age)
在Go語言中演侯,訪問一個(gè)結(jié)構(gòu)體的字段和調(diào)用一個(gè)類型的方法一樣姿染,都是使用點(diǎn)操作符"."
采用字面量初始化結(jié)構(gòu)體時(shí),初始化值的順序很重要秒际,必須和字段定義的順序一致
在person這個(gè)結(jié)構(gòu)體中悬赏,第一個(gè)字段是string類型的name,第二個(gè)字段是unit類型的age,所以在初始化的時(shí)候,初始化值的類型順序必須一致娄徊,才能編譯通過闽颇,也就是說在示例 {“飛雪無情”,30} 中,表示 name 的字符串飛雪無情必須在前嵌莉,表示年齡的數(shù)字 30 必須在后进萄。
那么是否可以不按照順序初始化呢?當(dāng)然可以锐峭,只不過需要指出子彈名稱中鼠,如下所示:
p:=person{age:30,name:"飛雪無情"}
其中,第一位我放了整型的 age沿癞,也可以編譯通過援雇,因?yàn)椴捎昧嗣鞔_的 field:value 方式進(jìn)行指定,這樣 Go 語言編譯器會(huì)清晰地知道你要初始化哪個(gè)字段的值椎扬。
有沒有發(fā)現(xiàn)惫搏,這種方式和map類型的初始化很像,都是采用冒號(hào)分隔蚕涤。Go語言盡可能的重用操作筐赔,不發(fā)明新的表達(dá)式,便于記憶和使用揖铜。
當(dāng)然你也可以只初始化子字段age茴丰,字段name使用默認(rèn)值的零值,如下面代碼所示天吓,仍然可以編譯通過贿肩。
p:=person{age:30}
子彈結(jié)構(gòu)體
結(jié)構(gòu)體的字段可以是任意類型,也包括自定義的結(jié)構(gòu)體類型龄寞。比如下面的代碼
type person struct {
name string
age uint
addr address
}
type address struct {
province string
city string
}
在這個(gè)示例中汰规,定義了2個(gè)結(jié)構(gòu)體:person 表示人,address表示地址物邑,在結(jié)構(gòu)體person中溜哮,有一個(gè)address類型的字段addr滔金,這就是自定義的結(jié)構(gòu)體
通過這種方式,用代碼描述現(xiàn)實(shí)中的實(shí)體會(huì)更匹配茬射,復(fù)用程度也更高鹦蠕。對于嵌套結(jié)構(gòu)體字段的結(jié)構(gòu)體,其初始化和正常的結(jié)構(gòu)體大同小異在抛,只需要根據(jù)字段對應(yīng)的類型初始化即可钟病,如下面的代碼所示:
p:=person{
age:30,
name:"飛雪無情",
addr:address{
province: "北京",
city: "北京",
},
}
如果需要訪問結(jié)構(gòu)體最里層的 province 字段的值,同樣也可以使用點(diǎn)操作符刚梭,只不過需要使用兩個(gè)點(diǎn)肠阱,如下面的代碼所示:
fmt.Println(p.addr.province)
第一個(gè)點(diǎn)獲取addr,第二個(gè)點(diǎn)獲取addr的province
接口
接口的定義
接口是和調(diào)用方的一種約定,他是一種高度抽象的類型朴读,不用和具體的實(shí)現(xiàn)細(xì)節(jié)綁定在一起屹徘。接口要做的是定義好約定,告訴調(diào)用方自己可以做點(diǎn)什么衅金,但不用知道他的內(nèi)部實(shí)現(xiàn)噪伊,這和我們見到的具體的類型如 int,map,slice等不一樣
接口的定義和結(jié)構(gòu)體稍微有些差別,雖然都以type關(guān)鍵字開始氮唯,但接口的關(guān)鍵字是interface鉴吹,表示自定義的類型是一個(gè)接口,也就是說Stringer是一個(gè)接口惩琉,他有一個(gè)方法String()string 整體如下面的代碼所示
type Stringer interface {
String() string
}
提示: Stringer 是Go SDK的一個(gè)的接口豆励,屬于fmt包
針對Stringer接口來說,他會(huì)告訴調(diào)用者可以通過他的String()方法獲取一個(gè)字符串這就是接口的約定瞒渠。至于這個(gè)字符串怎么獲得的良蒸,長什么樣,接口不關(guān)心伍玖,調(diào)用者也不用關(guān)心嫩痰,因?yàn)檫@些是由接口實(shí)現(xiàn)者來做的。
接口的實(shí)現(xiàn)
接口的實(shí)現(xiàn)者必須是一個(gè)具體的類型窍箍,繼續(xù)以person結(jié)構(gòu)體為例始赎,讓他來實(shí)現(xiàn)Stringer接口,如下代碼所示
func (p person) String() string {
return fmt.Sprintf("the name is %s,age is %d", p.age, p.age)
}
給結(jié)構(gòu)體類型Person定義一個(gè)方法仔燕,這個(gè)方法和接口里方法的簽名(名稱,參數(shù)和返回值)一樣魔招,這樣結(jié)構(gòu)體person就實(shí)現(xiàn)了Stringer接口
注意:如果一個(gè)接口有多個(gè)方法晰搀,那么需要實(shí)現(xiàn)接口的每個(gè)方法才算是實(shí)現(xiàn)了這個(gè)接口。
實(shí)現(xiàn)了Stringer接口后就可以使用了办斑,首先先來定義一個(gè)打印Stringer接口的函數(shù)外恕,如下所示:
func printString(s fmt.Stringer) {
fmt.Println(s.String())
}
這個(gè)被定義的函數(shù)printString杆逗,他接收一個(gè)Stringer接口類型的參數(shù),然后打印出Stringer接口的String方法返回的字符串
printString 這個(gè)函數(shù)的優(yōu)勢在于他是面向接口編程的鳞疲,只要一個(gè)類型實(shí)現(xiàn)了Stringer接口罪郊,都可以打印出對應(yīng)的字符串,都可以打印出對應(yīng)的字符串尚洽,而不用管具體的類型實(shí)現(xiàn)
因?yàn)閜erson 實(shí)現(xiàn)了 Stringer接口悔橄,所以變量p可以作為函數(shù)printString,可以用如下方式打印:
func printString(s fmt.Stringer){
fmt.Println(s.String())
}
這個(gè)被定義的函數(shù)pringString腺毫,它接收了一個(gè)Stringer接口類型的參數(shù)癣疟,然后打印出Stringer接口的String 方法返回的字符串。
printString這個(gè)函數(shù)的優(yōu)勢就在于他是面向接口編程的潮酒,只要一個(gè)類型實(shí)現(xiàn)了Stringer接口睛挚,都可以打印出對應(yīng)的字符串,而不用管具體的類型實(shí)現(xiàn)
因?yàn)閜erson 實(shí)現(xiàn)了Stringer接口急黎,所以變量p可以作為函數(shù)printString 的參數(shù)扎狱,可以用如下方式打印:
printString(p)
結(jié)果為
the name is 飛雪無情,age is 30
現(xiàn)在讓結(jié)構(gòu)體address也實(shí)現(xiàn)了Stringer 接口勃教,如下代碼所示
func (addr address) String() string{
return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}
因?yàn)榻Y(jié)構(gòu)體address也實(shí)現(xiàn)了Stringer接口淤击,所以pringString函數(shù)不用做任何改變,可以直接被使用荣回,打印出地址遭贸,如下所示:
printString(p.addr)
//輸出:the addr is 北京北京
這就是面向接口的好處,只要定義和調(diào)用方滿足約定心软,就可以使用壕吹,而不用管具體的實(shí)現(xiàn)。接口的實(shí)現(xiàn)者也可以更好的升級(jí)重構(gòu)删铃,而不會(huì)有任何的影響耳贬,因?yàn)榻涌诘募s定沒有變
值接收者和指針接收者
我們已經(jīng)知道,如果實(shí)現(xiàn)一個(gè)接口猎唁,必須實(shí)現(xiàn)這個(gè)接口提供的所有方法咒劲,而且定義一個(gè)方法,有值類型接收者和指針類型接收者2種诫隅。二者都可以調(diào)用方法腐魂,因?yàn)镚o語言編譯器自動(dòng)做了轉(zhuǎn)換,所以值類型接收者和指針類型接收者是等價(jià)的逐纬。但是在接口的實(shí)現(xiàn)中蛔屹,值類型接收者和指針類型接收者不一樣。
已經(jīng)驗(yàn)證了結(jié)構(gòu)體類型實(shí)現(xiàn)了 Stringer 接口豁生,那么結(jié)構(gòu)體對應(yīng)的指針是否也實(shí)現(xiàn)了該接口呢兔毒?我通過下面這個(gè)代碼進(jìn)行測試:
printString(&p)
測試后會(huì)發(fā)現(xiàn)漫贞,把變量 p 的指針作為實(shí)參傳給 printString 函數(shù)也是可以的,編譯運(yùn)行都正常育叁。這就證明了以值類型接收者實(shí)現(xiàn)接口的時(shí)候迅脐,不管是類型本身,還是該類型的指針類型豪嗽,都實(shí)現(xiàn)了該接口谴蔑。
示例中值接收者(p person)實(shí)現(xiàn)了Stringer接口,那么類型person和他的指針類型