一.? go方法
go方法:在函數的func和函數名間增加一個特殊的接收器類型,接收器可以是結構體類型或非結構體類型。接收器可以在方法內部訪問。創建一個接收器類型為Type的methodName方法。
func (t Type) methodName(parameter list) {}
go引入方法的原因:
1)go不是純粹的面向對象編程語言,而且Go不支持類。因此,基于類型的方法是一種實現和類相似行為的途徑。
2)相同名字的方法可以定義在不同的類型上,而相同名字的函數不被允許。
方法調用
t.methodName(parameter list)
指針接收器與值接收器
區別:指針接收器的方法內部的改變對外可見,而值接收器不會改變方法外部的變量。
對于指針接收器&T Type而言,(&T).methodName與T.methodName等價。
匿名字段的方法
屬于結構體的匿名字段的方法可以被直接調用,就好像這些方法是屬于定義了匿名字段的結構體一樣。
在方法中使用值接收器 與 在函數中使用值參數
當一個函數有一個值參數,它只能接受一個值參數。
當一個方法有一個值接收器,它可以接受值接收器和指針接收器。
package?main
import "fmt"
type rectangle struct { length int width int}
func area(r rectangle){ fmt.Printf("Area Function result: %d\n", (r.length * r.width))}
func (r rectangle)area(){ fmt.Printf("Area method result: %d\n", (r.length * r.width))}
func main(){ r := rectangle{ length: 10, width: 5, }area(r) r.area()p := &r// area(p) // cannot use p (type *rectangle) as type rectangle in argument to area p.area() //通過指針調用接收器}
在方法中使用指針接收器 與 在函數中使用指針參數
函數使用指針參數只接受指針,而使用指針接收器的方法可以使用值接收器和指針接收器。
在非結構體上的方法
為了在一個類型上定義一個方法,方法的接收器類型定義和方法的定義應該在同一個包中。
對于內建類型,如int,應該在文件中創建一個類型別名,然后創建一個以該類型別名為接收器的方法。
二.? go接口
接口是方法(方法簽名,method signature)的集合。當一個類型定義了接口中的所有方法,就稱它實現了該接口。與OOP類似,接口定義了一個類型應該具有的方法,由該類型決定如何實現這些方法。
type myInterface interface{ method1() method2()}
接口調用
永遠不要使用一個指針指向一個接口類型,因為它已經是一個指針。 函數參數為interface{}時可以接收任何類型參數,即使是指針類型,也應該是interface{},而不是*interface{}。
//interface definitiontype VowelsFinder interface { FindVowels() []rune}
type MyString string
//MyString implements VowelsFinderfunc (ms MyString) FindVowels() []rune { var vowels []rune for _, rune := range ms { if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' { vowels = append(vowels, rune) } } return vowels}name := MyString("Sam Anderson") var v VowelsFinder v = name // possible since MyString implements VowelsFinder fmt.Printf("Vowels are %c", v.FindVowels())
如果一個類型包含了接口中聲明的所有方法,那么它就隱式地實現了 Go 接口 。
接口的內部表示
可以把接口看作內部的一個元組?(type, value)
。?type
?是接口底層的具體類型(Concrete Type),而?value
?是具體類型的值。
(接口值是一個兩個字長度的數據結構,第一個字包含一個指向內部表的指針,內部表iTable存儲值類型信息以及方法集。第二個字是一個指向所存儲值的指針。)
一個接口的值,接口值,由兩個部分組成,一個具體的類型和那個類型的值。它們被稱為接口的動態類型和動態值。
對于Go語言這種靜態類型的語言,類型是編譯期的概念:因此一個類型不是一個值。
從概念上講,不論接口值多大,動態值總是可以容下它。
接口值可以使用和!=來進行比較。兩個接口值相等僅當他們是nil值或者它們的動態類型相同并且動態值也根據這個動態類型的操作相等。因為接口值是可比較的,所以它們可以用在map的鍵或者作為switch語句的操作數。
然而,如果兩個接口值的動態類型相同,但是這個動態類型是不可比較的(比如切片),將它們比較就會失敗并且panic。
var x interface{} = []int{1, 2, 3}fmt.Println(x == x) // panic: comparing uncomparable
type []int
考慮到這點,接口類型是非常與眾不同的。其它類型要么是安全的可比較類型(如基本類型和指針)要么是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必須要意識到潛在的panic。同樣的風險也存在于使用接口作為map的鍵或者switch的操作數。只能比較你非常確定它們的動態值是可比較類型的接口值。
type Test interface { Tester()}
type MyFloat float64
func (m MyFloat) Tester() { fmt.Println(m)}
func describe(t Test) { fmt.Printf("Interface type %T value %v\n", t, t)}
func main() { var t Test f := MyFloat(89.7) t = f describe(t) t.Tester()}
輸出:Interface type main.myFloat value 89.789.7
空接口
沒有包含方法的接口稱為空接口。空接口表示為?interface{}
。由于空接口沒有方法,因此所有類型都實現了空接口。
當指定參數為空接口時,可以接收任意類型,那如何獲取參數的值呢?? 通過類型斷言。v, ok := p.(int),判定參數是否為int并獲取參數值。
函數可以接收interface{}作為參數,但最好不要返回interface{}。
類型斷言
類型斷言用于提取接口的底層值(Underlying Value)。
在語法 i.(T)?中,接口 i 的具體類型是 T,該語法用于獲得接口的底層值。
v, ok := i.(T)
如果 i 的具體類型是 T,那么 v 賦值為 i 的底層值,而 ok 賦值為 true。
如果?i?的具體類型不是?T,那么?ok?賦值為?false,v?賦值為?T?類型的零值,此時程序不會報錯。
類型選擇(Type Switch)
類型選擇用于將接口的具體類型與很多 case 語句所指定的類型進行比較。它與一般的 switch 語句類似。
類型選擇的語法是 i.(type),獲取接口的類型,type是固定關鍵字。需要注意的是,只有接口類型才可以使用類型選擇。
還可以將一個類型和接口相比較。如果一個類型實現了接口,那么該類型與其實現的接口就可以互相比較。
type Describer interface { Describe()}
type Person struct { name string age int}func (p Person) Describe(){ fmt.Printf("%s is %d years old\n", p.name, p.age)}
func findType(i interface{}){ switch v := i.(type){ case Describer: v.Describe() default: fmt.Printf("unknown type\n") }}
func main(){ findType("wang") p := Person{ name: "qing", age: 25, }findType(p)}
在上面程序中,結構體?Person
?實現了?Describer
?接口。在第 19 行的 case 語句中,v
?與接口類型?Describer
?進行了比較。p
?實現了?Describer
,因此滿足了該 case 語句,于是當程序運行到第 32 行的?findType(p)
?時,程序調用了?Describe()
?方法。
package main
import "fmt"
type InterfaceA interface { Print() Get() string}
type InterfaceB interface { Get() string Print()}
type InterfaceC interface { Print()}
type People struct { name string}
func (p *People) Print() { fmt.Println(p.name)}
func (p *People) Get() string { return p.name}
// 只要兩個接口擁有相同的方法列表(次序不同不要緊),那么他們就是等價的,可以相互賦值
func main() { var a InterfaceA var b InterfaceB var c InterfaceC a = b p := &People{name: "wang"} a = p //接口賦值若錯誤,編譯時直接報錯, a = *p b = a a.Print() b.Print() v, ok := b.(*People) // 接口查詢若錯誤,編譯直接報錯, b.(People) fmt.Println(v, ok) c = a // 多方法接口可以賦值到少方法接口 c.Print() // b = c // InterfaceC does not implement InterfaceB(missing Get method) // b.Print()}
實現接口:指針接受者與值接受者
使用值接受者聲明的方法,接口既可以用值來調用,也能用指針調用。
對于使用指針接受者的方法,接口必須用一個指針或者一個可取得地址的值(&method)來調用。但接口中存儲的具體值(Concrete Value)并不能取到地址,對于編譯器無法自動獲取 a 的地址,于是程序報錯。
type Describer interface { Describe()}type Person struct { name string age int}
func (p Person) Describe() { // 使用值接受者實現 fmt.Printf("%s is %d years old\n", p.name, p.age)}
type Address struct { state string country string}
func (a *Address) Describe() { // 使用指針接受者實現 fmt.Printf("State %s Country %s", a.state, a.country)}
func main() { var d1 Describer p1 := Person{"Sam", 25} d1 = p1 d1.Describe() p2 := Person{"James", 32} d1 = &p2 d1.Describe()var d2 Describer a := Address{"Washington", "USA"}/* 如果下面一行取消注釋會導致編譯錯誤: cannot use a (type Address) as type Describer in assignment: Address does not implement Describer (Describe method has pointer receiver) */ //d2 = ad2 = &a // 這是合法的 // 因為在第 22 行,Address 類型的指針實現了 Describer 接口 d2.Describe()}
接口的嵌套
type SalaryCalculator interface { DisplaySalary()}
type LeaveCalculator interface { CalculateLeavesLeft() int}
type EmployeeOperations interface { SalaryCalculator LeaveCalculator}
接口的零值
接口的零值是 nil。對于值為 nil 的接口,其底層值(Underlying Value)和具體類型(Concrete Type)都為 nil。對于值為 nil 的接口,由于沒有底層值和具體類型,當我們試圖調用它的方法時,程序會產生 panic 異常。
一個接口值基于它的動態類型被描述為空或非空,可以通過w==nil或w!=nil來判斷接口值是否為空。
當且僅當動態值和動態類型都為 nil 時,接口類型值才為 nil。
w=nil將重置接口w的所有部分(類型和值)為nil。
警告:一個包含nil指針的接口可能不是nil接口。
var buf *bytes.Bufferf(buf)
func f(out io.Writer){ // ... if out != nil { out.Write([]byte("done!\n")) // painic: nil pointer dereference }}
當執行f(buf)時,f函數的out參數賦了一個bytes.Buffer的空指針,所以out的動態值是nil,但是它的動態類型是bytes.Buffer,意思是out變量是一個包含空指針的非空接口,所以out!=nil的結果依然是true。
正確的處理是 var buf io.Writer,因此可以避免一開始就將一個不完全的值賦值給這個接口。
Go接口最佳實踐
1)傾向于使用小的接口定義,很多接口只包含一個方法。? ? 如Reader,Writer,便于類型實現接口,方法太多,類型實現越麻煩。
2)較大的接口定義,可以由多個小接口定義組合而成。? 即接口的嵌套。
3)只依賴于必要功能的最小接口。方法或函數的接口參數的范圍或方法越小越好,這樣便于參數的調用,和方法或函數被其他程序調用。
如func StoreData(reader Reader) error{},能傳遞Reader就不傳遞ReadWriter。
4)接口一般有默認實現,應用時嵌套默認實現。
package main
import "fmt"
type Animal interface { Speak() SpeakTo(string)}
// default implementationtype Pet struct {}
func (p *Pet) Speak() { fmt.Print("Pet...")}
func (p *Pet) SpeakTo(host string){ p.Speak() fmt.Println("---", host)}
// real functiontype Dog struct { *Pet // include default struct}/*func (d *Dog) Speak() { fmt.Print("Dog")}*/
func (d *Dog) SpeakTo(host string){ d.Speak() // no implement, using default fmt.Println(host)}
func main(){ dog := new(Dog) dog.SpeakTo("Chao")}
歡迎加入Go語言的學習wx群:wdw11079533