GoLang的方法與介面
轉自:https://www.cnblogs.com/chenny7/p/4497969.html
方法
Go 語言中同時有函式和方法。方法就是一個包含了接受者(receiver)的函式,receiver可以是內建型別或者結構體型別的一個值或者是一個指標。所有給定型別的方法屬於該型別的方法集。
如下面的這個例子,定義了一個新型別Integer,它和int一樣,只是為它內建的int型別增加了個新方法Less()
type Integer int func (a Integer) Less(b Integer) bool { return a < b } func main() { var a Integer = 1 if a.Less(2) { fmt.Println("less then 2") } } 可以看出,Go語言在自定義型別的物件中沒有C++/Java那種隱藏的this指標,而是在定義成員方法時顯式聲明瞭其所屬的物件。
method的語法如下: func (r ReceiverType) funcName(parameters) (results) 當呼叫method時,會將receiver作為函式的第一個引數: funcName(r, parameters); 所以,receiver是值型別還是指標型別要看method的作用。如果要修改物件的值,就需要傳遞物件的指標。 指標作為Receiver會對例項物件的內容發生操作,而普通型別作為Receiver僅僅是以副本作為操作物件,並不對原例項物件發生操作。 func (a *Ingeger) Add(b Integer) { *a += b } func main() { var a Integer = 1 a.Add(3) fmt.Println("a =", a) // a = 4 } 如果Add方法不使用指標,則a返回的結果不變,這是因為Go語言函式的引數也是基於值傳遞。 注意:當方法的接受者是指標時,即使用值型別呼叫那麼方法內部也是對指標的操作。
Go語言沒有建構函式的概念,通常使用一個全域性函式來完成。例如:
func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
func main() {
rect1 := NewRect(1,2,10,20)
fmt.Println(rect1.width)
}
匿名組合
Go語言提供了繼承,但是採用了組合的語法,我們將其稱為匿名組合,例如:
type Base struct { name string } func (base *Base) Set(myname string) { base.name = myname } func (base *Base) Get() string { return base.name } type Derived struct { Base age int } func (derived *Derived) Get() (nm string, ag int) { return derived.name, derived.age } func main() { b := &Derived{} b.Set("sina") fmt.Println(b.Get()) } 在Base型別定義了get()和set()兩個方法,而Derived型別繼承了Base類,並改寫了Get()方法, 在Derived物件呼叫Set()方法,會載入基類對應的方法;而呼叫Get()方法時,載入派生類改寫的方法。
組合的型別和被組合的型別包含同名成員時, 會不會有問題呢?可以參考下面的例子:
type Base struct {
name string
age int
}
func (base *Base) Set(myname string, myage int) {
base.name = myname
base.age = myage
}
type Derived struct {
Base
name string
}
func main() {
b := &Derived{}
b.Set("sina", 30)
fmt.Println("b.name =",b.name, "\tb.Base.name =", b.Base.name)
fmt.Println("b.age =",b.age, "\tb.Base.age =", b.Base.age)
}
值語義和引用語義
值語義和引用語義的差別在於賦值,比如
b = a b.Modify()
如果b的修改不會影響a的值,那麼此型別屬於值型別;如果會影響a的值,那麼此型別是引用型別。
Go語言中的大多數型別都基於值語義,包括:
- 基本型別,如byte、int、bool、float32、string等;
- 複合型別,如arry、struct、pointer等;
C語言中的陣列比較特別,通過函式傳遞一個數組的時候基於引用語義,但是在結構體定義陣列變數的時候基於值語義。而在Go語言中,陣列和基本型別沒有區別,是很純粹的值型別,例如:
var a = [3] int{1,2,3} var b = a b[1]++ fmt.Println(a, b) // [1 2 3] [1 3 3]
從結果看,b=a賦值語句是陣列內容的完整複製,要想表達引用,需要用指標:
var a = [3] int{1,2,3} var b = &a // 引用語義 b[1]++ fmt.Println(a, b) // [1 3 3] [1 3 3]
介面
Interface 是一組抽象方法(未具體實現的方法/僅包含方法名引數返回值的方法)的集合,如果實現了 interface 中的所有方法,即該類/物件就實現了該介面。
Interface 的宣告格式:
type interfaceName interface { //方法列表 }
Interface 可以被任意物件實現,一個型別/物件也可以實現多個 interface;
interface的變數可以持有任意實現該interface型別的物件。
如下面的例子:
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名欄位
school string
loan float32
}
type Employee struct {
Human //匿名欄位
company string
money float32
}
//Human實現SayHi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human實現Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
//Employee過載Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone)
}
// Interface Men被Human,Student和Employee實現
// 因為這三個型別都實現了這兩個方法
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
//定義Men型別的變數i
var i Men
//i能儲存Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//i也能儲存Employee
i = tom
fmt.Println("This is tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
//定義了slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//這三個都是不同型別的元素,但是他們實現了interface同一個介面
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x{
value.SayHi()
}
}
空介面
空interface(interface{})不包含任何的method,正因為如此,所有的型別都實現了空interface。空interface對於描述起不到任何的作用(因為它不包含任何的method),但是空interface在我們需要儲存任意型別的數值的時候相當有用,因為它可以儲存任意型別的數值。它有點類似於C語言的void*型別。
// 定義a為空介面
var a interface{}
var i int = 5
s := "Hello world"
// a可以儲存任意型別的數值
a = i
a = s
interface的變數裡面可以儲存任意型別的數值(該型別實現了interface),那麼我們怎麼反向知道這個
interface變數裡面實際儲存了的是哪個型別的物件呢?目前常用的有兩種方法:switch測試、Comma-ok斷言。
switch測試:
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//列印
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list{
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
Comma-ok斷言:
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
}
嵌入介面
正如struct型別可以包含一個匿名欄位,interface也可以巢狀另外一個介面。
如果一個interface1作為interface2的一個嵌入欄位,那麼interface2隱式的包含了interface1裡面的method。