首先间驮,go是一門靜態(tài)類型語言磕洪。即編譯器知道每一個值的類型焙糟。
如何定義一個類型炬藤?
A: 使用關(guān)鍵字type和struct:
type user struct {
name string
email string
ext int
privileged bool
}
注意每一行沒有逗號。
創(chuàng)建一個類型的值:
var rain user
lisa := user {
nae: "Lisa",
email: "lisa@email.com",
ext: 123,
privileged: true,
}
lisa1 := user{"Lisa", "lisa@email.com", 123, true} //最后一個逗號沒有
注意每一行都有逗號饺窿,最后一行也有歧焦。
struct的屬性可以是struct類型:
type admin struct {
u user
level string
}
fred := admin {
u: user {
name: "Fred",
email: "fred@email.com",
ext: 123,
privileged: true,
},
level: "super",
}
另一種創(chuàng)建類型的方式:
type Duration int64
注意此時,編譯器認為Duration和int64不是同一種類型肚医。此時绢馍,我們稱int64是Duration的"base type"
方法
方法是一種給類型增加行為的方式。
type user struct {
name string
email string
}
func (u user) notify() {
fmt.Printf("Sending User Email to %s<%s>\n", u.name, u.email)
}
func (u *user) changeEmail(email string) {
u.email = email
}
func和方法名之間的參數(shù)稱為“receiver"忍宋。當(dāng)一個函數(shù)具有receiver的時候痕貌,我們就稱該函數(shù)為方法。
receiver具有兩種類型:
- value receiver糠排。即值傳遞舵稠。
- pointer receiver。傳遞的是類型的指針
兩個不同類型的方法都可以被類型的值或者指針調(diào)用入宦。如:
bill := user{"bill", "bill@email.com"}
bill.notify()
lisa := &user{"lisa", "lisa@email.com"}
lisa.changeEmail("lisayou")
lisa.notify()
go會自動進行(*lisa).notify()
和(&bill).changeEmail()
的轉(zhuǎn)換哺徊。
一般來說,當(dāng)使用value receiver的時候表明不需要改變值乾闰,使用pointer receiver的時候需要改變值落追。
接口
A: 使用type和interface定義接口:
type notifier interface{
notify()
}
注意這里沒有定義接口的返回值和參數(shù),因為返回值和參數(shù)都沒有涯肩。
Q: 如何使用接口轿钠?
A: 見代碼:
func sendNotification(n notifier) {
n.notify()
}
Q: 如何實現(xiàn)接口巢钓?
A: 類型的方法具有同接口一樣的名字、參數(shù)和返回值疗垛,就認為該類型實現(xiàn)了接口症汹。無需顯示聲明。
比如:
type user struct {
name string
email string
}
func (u user) notify(){
}
此時user實現(xiàn)了notifier接口贷腕。
Q: point receiver的方法算接口實現(xiàn)嗎背镇?
A: 看一個例子:
type user struct {
name string
email string
}
func (u *user) notify(){
}
此時user算是實現(xiàn)了notifier了嗎?
先來了解一下methods sets:
Method sets define the set of methods that are associated with values or pointers of a given type. The type of receiver used will determine whether a method is associated with a value, pointer, or both.
method sets定義了一個值或者指針類型的方法集泽裳。方法的receiver的類型決定了一個方法是同值瞒斩、指針或者兩者都進行了關(guān)聯(lián)。聽起來一頭霧水涮总。
這個表的意思是:
- 類型T的值的method sets僅包含value receiver的方法
- 類型
*T
的值的method sets既包含value receiver的方法也包含pointer receiver的方法
從method receiver的角度來看:
這個的意思就是:
- 如果使用pointer receiver實現(xiàn)了某個接口胸囱,那么實際上是該類型的指針實現(xiàn)了該接口
- 如果使用value receiver實現(xiàn)了某個接口,那么該類型的值和指針類型都實現(xiàn)了該接口
上面的代碼的方法是pointer receiver瀑梗,所以僅有user類型的指針類型實現(xiàn)了接口旺矾。
為什么這么定義,是因為并不是總能獲取某個值的地址:
type duration int
func (d *duration) pretty() string {
return fmt.Sprintf("Duration: %d", *d)
}
func main() {
duration(42).pretty()
}
Q: 什么是多態(tài)夺克?
A: 簡單來說:多態(tài)就是,聲明的時候是接口嚎朽,傳入的時候是該接口的實現(xiàn)铺纽。不同的實現(xiàn)具有不同的行為。
Q: type embedding是什么哟忍?
A: 先看原文:
This is accomplished through type embedding. It works by taking an existing type and declaring that type within the declaration of a new struct type. The type that is embedded is then called an inner type of the new outer type.
簡單來說狡门,就是將一個已經(jīng)存在的類型(type)放在一個新類型中聲明。
被嵌入的類型稱為inner type锅很,新類型稱為outer type其馏。
有什么作用呢?
Through inner type promotion, identifiers from the inner type are promoted up to the outer type. These promoted identifiers become part of the outer type as if they were declared explicitly by the type itself.
簡單來說爆安,inner type的屬性和方法就像是outer type自己定義的一樣叛复。同時outer type也可以覆蓋inner type的屬性和方法∪硬郑看一個例子:
package main
import (
"fmt"
)
type user struct {
name string
emaill string
}
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
}
type admin struct {
user // Embedded Type
level string
}
func main() {
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
ad.user.notify()
ad.notify()
}
我們也可以覆蓋user里面的方法褐奥,比如:
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n", a.name, a.email)
}
此時,由于admin內(nèi)嵌了user翘簇,user實現(xiàn)了notifier接口撬码,因此admin其實也實現(xiàn)了notifier接口。
導(dǎo)出和不導(dǎo)出identifiers
簡單來說版保,就是包里面的小寫字母開頭的標(biāo)識符是不導(dǎo)出的呜笑,大寫開頭的是導(dǎo)出的夫否。
先來看一個例子:
// counters/counters.go
package counters
type alertCounter int
func New(value int) alertCounter {
return alertCounter(value)
}
// listing68.go
package main
import (
"fmt"
"github.com/goinaction/code/chapter5/listing68/counters"
)
func main() {
counter := counters.New(10)
fmt.Printf("Counter: %d\n", counter)
}
在counters.go里面的alertCounter是小寫字母開頭,因此該identifer沒有導(dǎo)出叫胁,不能在下個文件的main函數(shù)里面使用凰慈。但是New方法是導(dǎo)出了的。因此曹抬,main函數(shù)里面的代碼沒有錯誤溉瓶。
但是有個問題,alertCounter沒有導(dǎo)出谤民,怎么可以使用呢堰酿?
見原文:
This is possible for two reasons. First, identifiers are exported or unexported, not values. Second, the short variable declaration operator is capable of inferring the type and creating a variable of the unexported type. You can never explicitly create a variable of an unexported type, but the short variable declaration operator can.
簡單來說:
1,導(dǎo)出或者不導(dǎo)出的是identifiers张足,不是values触创。也就是說小寫字母的類型的值是可以在package外使用的;
2为牍,不能顯式使用未導(dǎo)出的identifier哼绑,但是可以隱式使用。也就是使用:=
是可以的碉咆。
看一個完整版:
// entities/entities.go
package entities
type user struct{
Name string
Email string
}
type admin struct{
user
Rights int
}
// listing74.go
package main
import (
"fmt"
"github.com/goinaction/code/chapter5/listing74/entities"
)
func main() {
a := entities.Admin{
Rights: 10,
}
a.Name = "Bill"
a.Email = "bill@email.com"
fmt.Printf("User: %v\n", a)
}