在政府網(wǎng)站建設(shè)工作會上的講話百度推廣的方式有哪些
go貌似都沒有聽說過繼承,當然這個繼承不像c++中通過class類的方式去繼承,還是通過struct的方式,所以go嚴格來說不是面向?qū)ο缶幊痰恼Z言,c++和java才是,不過還是可以基于自身的一些的特性實現(xiàn)面向?qū)ο蟮墓δ?#xff0c;面向?qū)ο笕筇匦?#xff1a;封裝、繼承、多態(tài)
封裝
屬性
go是接住結(jié)構(gòu)體struct
實現(xiàn)類的聲明,比如要定義個學生類,學生類和animal類是面向?qū)ο笫纠榻B中重點,可以通過如下方式實現(xiàn):
type Student struct {id uintname stringmale boolscore float64
}
結(jié)構(gòu)體名或者說類名為 Student
,并且包含了 id
、name
、male
、score
四個屬性,Go 語言中也不支持構(gòu)造函數(shù)、析構(gòu)函數(shù),取而代之地,可以通過定義形如 NewXXX
這樣的全局函數(shù)(首字母大寫)作為類的初始化函數(shù)
func NewStudent(id uint, name string, male bool, score float64) *Student {return &Student{id, name, male, score}
}
在這個函數(shù)中,我們通過傳入的屬性字段對 Student 類進行初始化并返回一個指向該類的指針
在 Go 語言中,未進行顯式初始化的變量都會被初始化為該類型的零值,例如 bool 類型的零值為 false,int 類型的零值為 0,string 類型的零值為空字符串,float 類型的零值為 0.0。接著就可以調(diào)用了
package main
import ("fmt"student2 "student/stu"
)
func main() {student := student2.NewStudent(1, "zhiyu",false, 100)fmt.Println(student)
}
成員函數(shù)
getXXX方法
要為 Go 類定義成員方法,需要在 func 和方法名之間聲明方法所屬的類型(有的地方將其稱之為接收者聲明),以 Student 類為例,要為其定義獲取 name 值的方法,可以這么做:
func (s Student) GetName() string {return s.name
}
這樣就可以使用GetName
方法獲取name
屬性,通過在函數(shù)簽名中增加接收者聲明的方式定義了函數(shù)所歸屬的類型,這個時候,函數(shù)就不再是普通的函數(shù),而是類的成員方法了
setXXX方法
getName
是一個只讀方法,setName
是一個可寫的方法,所以需要使用指針的方式進行傳參,在類的成員方法中,可以通過聲明的類型變量來訪問類的屬性和其他方法
func (s *Student) SetName(name string) {s.name = name
}
可以把接收者類型為指針的成員方法叫做指針方法
,把接收者類型為非指針的成員方法叫做值方法
,二者的區(qū)別在于值方法傳入的結(jié)構(gòu)體變量是值類型(類型本身為指針類型除外),因此傳入函數(shù)內(nèi)部的是外部傳入結(jié)構(gòu)體實例的值拷貝,修改不會作用到外部傳入的結(jié)構(gòu)體實例.
另外,需要聲明的是,在 Go 語言中,當我們將成員方法 SetName
所屬的類型聲明為指針類型時,嚴格來說,該方法并不屬于 Student
類,而是屬于指向 Student
的指針類型,所以,歸屬于 Student
的成員方法只是 Student
類型下所有可用成員方法的子集,歸屬于 *Student
的成員方法才是 Student
類完整可用方法的集合。
在調(diào)用值方法和指針方法時,需要記住以下兩條準則:
- 值方法可以通過指針和值類型實例調(diào)用,指針類型實例調(diào)用值方法時會自動解引用;
- 指針方法只能通過指針類型實例調(diào)用,但有一個例外,如果某個值是可尋址的(或者說左值),那么編譯器會在值類型實例調(diào)用指針方法時自動插入取地址符,使得在此情形下看起來像指針方法也可以通過值來調(diào)用
左值和右值
結(jié)合上面的兩條準則使用示例說明如下:
package mainimport "fmt"
type Student struct {id uintname stringscore float64
}func NewStudent(id uint, name string, score float64) *Student {return &Student{id: id, name: name, score: score}
}func NewStudentV2(id uint, name string, score float64) Student {return Student{id: id, name: name, score: score}
}func (s Student) GetName() string {return s.name
}func (s *Student) SetName(name string) {s.name = name
}func main() {s := NewStudent(1, "zhiyu", 100)s.SetName("zhiyu-1") // ok 正常調(diào)用指針方法fmt.Println(s.GetName()) // ok 指針調(diào)用值方法自動解引用: (*s).GetName()s2 := NewStudentV2(2, "zhiyu", 90)s2.SetName("zhiyu-2") // ok s2 是可尋址的左值,所以實際調(diào)用: (&s2).SetName("zhiyu-2")fmt.Println(s2.GetName()) // ok 正常調(diào)用值方法NewStudent(3, "zhiyu", 80).SetName("zhiyu-3") // ok 正常調(diào)用指針方法NewStudentV2(4, "zhiyu", 99).SetName("zhiyu-4") // err 值類型調(diào)用指針方法
}
之所以可以直接在 s2
值實例上調(diào)用 SetName
指針方法,是因為 s2
是可尋址的,Go 語言底層會自動將 s2
轉(zhuǎn)化為對應(yīng)的指針類型 &s2
,所以真正調(diào)用的代碼是 (&s2).SetName("zhiyu-2")
,而通過 NewStudentV2
(…)返回實例調(diào)用 SetName
時,則會報錯,因為 NewStudentV2
(…) 是一個不可以尋址的右值。
所謂左值就是可以出現(xiàn)在賦值等號左邊的值,而右值只能出現(xiàn)在賦值等號右邊,比如函數(shù)返回值、字面量、常量值等。左值可尋址,右值不可尋址。
總結(jié)下來,就是一個自定義數(shù)據(jù)類型的方法集合中僅會包含它的所有「值方法」,而該類型對應(yīng)的指針類型包含的方法集合才囊括了該類型的所有方法,包括所有「值方法」和「指針方法」,指針方法可以修改所屬類型的屬性值,而值方法則不能。
值方法|指針方法
有如下情形的考量時,需要將類方法定義為指針方法:
- 數(shù)據(jù)一致性:方法需要修改傳入的類型實例本身;
- 方法執(zhí)行效率:如果是值方法,在方法調(diào)用時一定會產(chǎn)生值拷貝,而大對象拷貝代價很大。
通常我們都會選擇定義指針方法
繼承
Go
雖然沒有直接提供繼承相關(guān)的語法實現(xiàn),但是通過組合的方式間接實現(xiàn)類似功能,所謂組合,就是將一個類型嵌入到另一個類型,從而構(gòu)建新的類型結(jié)構(gòu)。使用了Student
類介紹封裝,接著使用Animal
示例繼承
package anitype Animal struct {Name string
}func (a Animal) Call() string {return "動物叫聲"
}func (a Animal) FavorFood() string {return "愛吃的食物..."
}func (a Animal) GetName() string {return a.Name
}
所有的動物都有以上的三種方法,現(xiàn)在Dog
實現(xiàn)繼承Animal
,新增一個dog.go,一般會給自家的愛狗起一個別名
package anitype Dog struct {AnimalAlias string
}func (d Dog) GetAliasName() string {return d.Alias
}
main.go
中如下所示
package mainimport ("animal/ani""fmt"
)func main() {animal := ani.Animal{"旺財"}dog := ani.Dog{animal, "旺旺"}fmt.Println(dog.GetName())fmt.Println(dog.Call())fmt.Println(dog.GetAliasName())
}
這就實現(xiàn)了繼承的功能。注意在初始化子類時的順序和struct中的定義順序是一致的
多態(tài)
所謂多態(tài)就是一個函數(shù)的多種形態(tài),比如在子類中定義同名函數(shù)來覆蓋父類方法,專業(yè)屬于稱之為方法重寫
。每個動物的喜歡吃的食物和叫聲不同,這里實現(xiàn)下Dog
重寫方法,在dog.go
中
package anitype Dog struct {AnimalAlias string
}func (d Dog) GetAliasName() string {return d.Alias
}func (d Dog) FavorFood() string {return "大棒骨"
}func (d Dog) Call() string {return "旺旺"
}
重寫go build
,并在terminal中在animal
根目錄下
當然子類也可以直接調(diào)用父類Animal
中的方法
fmt.Print(dog.Animal.Call())
fmt.Println(dog.Call())
fmt.Print(dog.Animal.FavorFood())
fmt.Println(dog.FavorFood())