類型
在聲明一個(gè)新類型之后舍哄,聲明一個(gè)該類型的方法之前,需先確定:這個(gè)類型的本質(zhì)是什么誊锭?如果給這個(gè)類型增加或刪除某個(gè)值表悬,是要?jiǎng)?chuàng)建一個(gè)新值,還是要更改當(dāng)前的值丧靡?如果要新值蟆沫,就選擇值接收者;如果要修改當(dāng)前值温治,就選擇指針接收者饭庞。這決定了 程序內(nèi)部傳遞這個(gè)類型的值的方式:是按值做傳遞,還是按指針做傳遞熬荆。
1. 內(nèi)置類型
包含數(shù)值類型舟山、字符串類型和布爾類型。
這些類型本質(zhì)上都是原始類型,在操作其值時(shí)累盗,應(yīng)該傳遞其對(duì)應(yīng)的值的副本寒矿。
2. 引用類型
包含切片、映射若债、通道符相、接口和函數(shù)類型。
每個(gè)引用類型創(chuàng)建的標(biāo)頭值(引用類型創(chuàng)建的變量)蠢琳,都包含一個(gè)指向底層數(shù)據(jù)結(jié)構(gòu)的指針啊终。
所以通過復(fù)制來傳遞一個(gè)引用類型的值的副本,本質(zhì)上就是在共享底層數(shù)據(jù)結(jié)構(gòu)傲须。
3. 結(jié)構(gòu)類型
待補(bǔ)充
但結(jié)構(gòu)類型的本質(zhì)既可以是原始的蓝牲,也可以是非原始的。
4. 看一個(gè)標(biāo)準(zhǔn)庫中的接口 及 實(shí)現(xiàn)例子
4.1 io.Writer接口
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
Write(p []byte) (n int, err error)
}
4.2 bytes.Buffer實(shí)現(xiàn)了該接口
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m := b.grow(len(p))
return copy(b.buf[m:], p), nil
}
4.3 接口實(shí)現(xiàn)多態(tài)
當(dāng)方法的接收參數(shù)是某個(gè)接口時(shí)躏碳,可以將不同的實(shí)現(xiàn)該接口的值作為參數(shù)傳遞給方法搞旭,從而實(shí)現(xiàn)采取不同行為的能力。
方法fmt.Fprintf
的第一個(gè)參數(shù)是io.Writer
接口
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
方法io.Copy
的第一個(gè)參數(shù)也是io.Writer
接口
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
4.4 簡(jiǎn)單的例子展示io多態(tài)
func ByteBuffer() {
var b bytes.Buffer
//將字符串寫入Buffer
b.Write([]byte("Hello go"))
//使用Fprintf將字符串拼接到Buffer
fmt.Fprintf(&b, "菇绵, nice to meet you")
//將Buffer的內(nèi)容寫到Stdout
io.Copy(os.Stdout, &b)
}
最終輸出結(jié)果:
Hello go肄渗, nice to meet you
5. Go語言的接口規(guī)則
因?yàn)樯婕暗街羔槪?guī)則相比Java來說略微復(fù)雜一些咬最。
Go
語言的方法有接收者這一說詞翎嫡,從Java
的角度來看,就是方法的調(diào)用者永乌。在Java
中惑申,調(diào)用者就是調(diào)用者。但是Go
語言中翅雏,分值接收者與指針接收者圈驼。但規(guī)則不外乎如下兩種:
如果一個(gè)接口的實(shí)現(xiàn)類型,用指針接收者來作為方法的接收者(其實(shí)就是Java的調(diào)用者)望几,那么只能用該類型的指針對(duì)象來調(diào)用該接口绩脆。
如果一個(gè)接口的實(shí)現(xiàn)類型,用值接收者來作為方法的接收者橄抹,那么既可以用指針對(duì)象也可以用值的副本來調(diào)用該接口靴迫。
以下是Go
語言給出的方法集規(guī)則:
Values | Methods Receivers | remark |
---|---|---|
T | (t T) | 如2 |
*T | (t T) and (t *T) | 如1 |
Methods Receivers | Values | remark |
---|---|---|
(t T) | T and *T | 如2 |
(t *T) | *T | 如1 |
5. 多態(tài)
如下實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的多態(tài):
//person接口
type personInter interface {
sayName()
}
//用戶
type user struct {
name string
}
func (u *user) sayName() {
fmt.Printf("User name is %s \n", u.name)
}
//管理員
type admin struct {
name string
}
func (u *admin) sayName() {
fmt.Printf("Admin name is %s \n", u.name)
}
func Test() {
logger := user{"logger"}
//letsSayYourName(logger) //這是錯(cuò)誤調(diào)用,不能用值的副本logger去調(diào)用接收者為指針接收者的實(shí)現(xiàn)方法
letsSayYourName(&logger)
admin := admin{"admin"}
letsSayYourName(&admin)
}
//方法入?yún)榻涌诼ナ模灰獙?shí)現(xiàn)了該接口的實(shí)現(xiàn)類型都可作為入?yún)?func letsSayYourName(person personInter) {
person.sayName()
}
最終輸出:
User name is logger
Admin name is admin
6. 嵌入類型
先介紹一下Go
語言所謂的嵌入類型:
嵌入類型是將已有的類型直接聲明在新的結(jié)構(gòu)類型中玉锌,被嵌入的類型被稱為新的外部類型的內(nèi)部類型。
個(gè)人理解Go
語言中的嵌入類型類似于Java
中的組合疟羹。
6.1 示例1 -- 外部類型調(diào)用內(nèi)嵌類型的方法
type animal struct {
name string
}
func (u *animal) sayName() {
fmt.Printf("name is %s \n", u.name)
}
//管理員類型主守,內(nèi)嵌一個(gè)user類型
type person struct {
animal //嵌入類型
name string
}
func TestInnerType() {
ad := person{animal{"logger"}, "admin"}
ad.animal.sayName()
ad.sayName()
}
輸出:
name is logger
name is logger
小結(jié):
- 可以將內(nèi)嵌類型的方法提升到外部類型
-
ad.sayName()
調(diào)用內(nèi)嵌類型的方法輸出的還是內(nèi)嵌類型的屬性
6.2 示例2 -- 外部類型調(diào)用內(nèi)嵌類型的實(shí)現(xiàn)接口
//接口
type foodInter interface {
sayFoodName()
}
//蘋果
type apple struct {
name string
}
//超市
type market struct {
apple //內(nèi)嵌類型 -- 蘋果
}
func (a *apple) sayFoodName() {
fmt.Printf("food name is %s \n", a.name)
}
func TestInnerTypeInterface() {
walmart := market{ apple{"ApplyX"}}
//重點(diǎn)是這句
//用于實(shí)現(xiàn)接口的內(nèi)部類型的方法禀倔,被提升到了外部類型
commonSayName(&walmart)
}
func commonSayName(food foodInter) {
food.sayFoodName()
}
輸出:
food name is ApplyX
小結(jié):
- 由于內(nèi)部類型的提升,內(nèi)部類型實(shí)現(xiàn)的接口會(huì)自動(dòng)提升到外部類型丸逸。這意味著由于內(nèi)部類型的實(shí)現(xiàn)蹋艺,外部類型也同樣實(shí)現(xiàn)了這個(gè)接口剃袍。
6.3 示例3 -- 外部類型覆蓋內(nèi)嵌類型的實(shí)現(xiàn)接口
相比于示例2黄刚,增加了類型market
自己的實(shí)現(xiàn)方法以此覆蓋內(nèi)嵌類型的實(shí)現(xiàn)接口,并在類型market
中添加了屬性marketName
以便輸出展示民效。
//接口
type foodInter interface {
sayFoodName()
}
//蘋果
type apple struct {
name string
}
//超市
type market struct {
marketName string
apple //內(nèi)嵌類型 -- 蘋果
}
func (a *apple) sayFoodName() {
fmt.Printf("food name is %s \n", a.name)
}
func (m *market) sayFoodName() {
fmt.Printf("market name is %s \n", m.marketName)
}
func TestInnerTypeInterface() {
walmart := market{ "walmart", apple{"ApplyX"}}
//重點(diǎn)是這句
//用于實(shí)現(xiàn)接口的內(nèi)部類型的方法憔维,被提升到了外部類型
commonSayName(&walmart)
}
func commonSayName(food foodInter) {
food.sayFoodName()
}
輸出:
market name is walmart
小結(jié):
- 由于內(nèi)部類型的提升,內(nèi)部類型實(shí)現(xiàn)的接口會(huì)自動(dòng)提升到外部類型畏邢。這意味著由于內(nèi)部類型的實(shí)現(xiàn)业扒,外部類型也同樣實(shí)現(xiàn)了這個(gè)接口。
7. 公開或未公開的標(biāo)識(shí)符
類似于Java
中的public舒萎、private
等程储,你可以控制方法或變量的訪問權(quán)限。Go
語言當(dāng)然也提供了這種功能臂寝。
Go
語言支持從包里公開或隱藏標(biāo)識(shí)符章鲤。如果一個(gè)標(biāo)識(shí)符,如下示例中的counter
以小寫字母開頭咆贬,即包外代碼不可見(類似Java的private)败徊,如果一個(gè)標(biāo)識(shí)符,如下示例中的PublicCounter
以大寫字母開頭掏缎,這個(gè)標(biāo)識(shí)符就是公開的皱蹦,即包外代碼可見(類似Java的public)。
7.1 原始類型
//聲明一個(gè)未公開的計(jì)數(shù)器
type counter int
//聲明一個(gè)公開的計(jì)數(shù)器
type PublicCounter int
當(dāng)然眷蜈,我們也可以給未公開的標(biāo)識(shí)符沪哺,如counter
寫一個(gè)公開的取值函數(shù),或者寫一個(gè)工廠函數(shù)新建一個(gè)counter
酌儒。
//取值函數(shù)
func GetCounter(value int) counter {
return counter
}
//工廠函數(shù)
func New(value int) counter {
return counter(value)
}
工廠函數(shù)命名為New
只是Go
語言中的一個(gè)習(xí)慣辜妓。
7.2 結(jié)構(gòu)類型中的未公開字段
結(jié)構(gòu)類型中帶有未公開類型的字段
//公開的用戶類型
type Admin struct {
Name string //公開的Name字段
email string //未公開的email字段
}
如果在其它包中創(chuàng)建該類型,并賦值email
字段今豆,在定義階段是不會(huì)報(bào)錯(cuò)的嫌拣,如下不會(huì)直接報(bào)錯(cuò)(identifier
是包名):
ad := identifier.Admin{"admin", "admin@email.com"}
但是在運(yùn)行的時(shí)候,會(huì)報(bào)錯(cuò)呆躲,錯(cuò)誤如下:
# command-line-arguments
.\main.go:43: implicit assignment of unexported field 'email' in identifier.Admin literal
7.3 訪問未公開的內(nèi)嵌對(duì)象中的公開字段
在identifier
包中定義如下結(jié)構(gòu)類型异逐,apple
是未公開的結(jié)構(gòu)類型,但是其Name
字段是公開的插掂。
type apple struct {
Name string
}
//公開類型的Tree對(duì)象
type Tree struct {
TreeName string //公開類型的TreeName
apple //未公開的apple類型
}
在其它包中灰瞻,可以利用內(nèi)部對(duì)象屬性升級(jí)為外部對(duì)象的特性腥例,為tree
的未公開內(nèi)嵌類型apple
的公開字段Name
設(shè)值。
tree := identifier.Tree{TreeName: "MoneyTree"}
tree.Name = "apply"
fmt.Printf("%v \n", tree)