Go語(yǔ)言沒(méi)有沿襲傳統(tǒng)面向?qū)ο缶幊讨械闹T多概念,比如繼承、虛函數(shù)、構(gòu)造函數(shù)和析構(gòu)函數(shù)、隱藏的this指針等。
方法
Go 語(yǔ)言中同時(shí)有函數(shù)和方法。方法就是一個(gè)包含了接受者(receiver)的函數(shù),receiver可以是內(nèi)置類(lèi)型或者結(jié)構(gòu)體類(lèi)型的一個(gè)值或者是一個(gè)指針。所有給定類(lèi)型的方法屬于該類(lèi)型的方法集。
如下面的這個(gè)例子,定義了一個(gè)新類(lèi)型Integer,它和int一樣,只是為它內(nèi)置的int類(lèi)型增加了個(gè)新方法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語(yǔ)言在自定義類(lèi)型的對(duì)象中沒(méi)有C++/Java那種隱藏的this指針,而是在定義成員方法時(shí)顯式聲明了其所屬的對(duì)象。
method的語(yǔ)法如下:
func (r ReceiverType) funcName(parameters) (results)
當(dāng)調(diào)用method時(shí),會(huì)將receiver作為函數(shù)的第一個(gè)參數(shù):
所以,receiver是值類(lèi)型還是指針類(lèi)型要看method的作用。如果要修改對(duì)象的值,就需要傳遞對(duì)象的指針。
指針作為Receiver會(huì)對(duì)實(shí)例對(duì)象的內(nèi)容發(fā)生操作,而普通類(lèi)型作為Receiver僅僅是以副本作為操作對(duì)象,并不對(duì)原實(shí)例對(duì)象發(fā)生操作。
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返回的結(jié)果不變,這是因?yàn)镚o語(yǔ)言函數(shù)的參數(shù)也是基于值傳遞。
注意:當(dāng)方法的接受者是指針時(shí),即使用值類(lèi)型調(diào)用那么方法內(nèi)部也是對(duì)指針的操作。
之前說(shuō)過(guò),Go語(yǔ)言沒(méi)有構(gòu)造函數(shù)的概念,通常使用一個(gè)全局函數(shù)來(lái)完成。例如:
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語(yǔ)言提供了繼承,但是采用了組合的語(yǔ)法,我們將其稱(chēng)為匿名組合,例如:
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類(lèi)型定義了get()和set()兩個(gè)方法,而Derived類(lèi)型繼承了Base類(lèi),并改寫(xiě)了Get()方法,在Derived對(duì)象調(diào)用Set()方法,會(huì)加載基類(lèi)對(duì)應(yīng)的方法;而調(diào)用Get()方法時(shí),加載派生類(lèi)改寫(xiě)的方法。
組合的類(lèi)型和被組合的類(lèi)型包含同名成員時(shí), 會(huì)不會(huì)有問(wèn)題呢?可以參考下面的例子:
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) }
值語(yǔ)義和引用語(yǔ)義
值語(yǔ)義和引用語(yǔ)義的差別在于賦值,比如
如果b的修改不會(huì)影響a的值,那么此類(lèi)型屬于值類(lèi)型;如果會(huì)影響a的值,那么此類(lèi)型是引用類(lèi)型。
Go語(yǔ)言中的大多數(shù)類(lèi)型都基于值語(yǔ)義,包括:
- 基本類(lèi)型,如byte、int、bool、float32、string等;
- 復(fù)合類(lèi)型,如arry、struct、pointer等;
C語(yǔ)言中的數(shù)組比較特別,通過(guò)函數(shù)傳遞一個(gè)數(shù)組的時(shí)候基于引用語(yǔ)義,但是在結(jié)構(gòu)體定義數(shù)組變量的時(shí)候基于值語(yǔ)義。而在Go語(yǔ)言中,數(shù)組和基本類(lèi)型沒(méi)有區(qū)別,是很純粹的值類(lèi)型,例如:
var a = [3] int{1,2,3} var b = a b[1]++ fmt.Println(a, b) // [1 2 3] [1 3 3]
從結(jié)果看,b=a賦值語(yǔ)句是數(shù)組內(nèi)容的完整復(fù)制,要想表達(dá)引用,需要用指針:
var a = [3] int{1,2,3} var b = &a // 引用語(yǔ)義 b[1]++ fmt.Println(a, b) // [1 3 3] [1 3 3]
接口
Interface 是一組抽象方法(未具體實(shí)現(xiàn)的方法/僅包含方法名參數(shù)返回值的方法)的集合,如果實(shí)現(xiàn)了 interface 中的所有方法,即該類(lèi)/對(duì)象就實(shí)現(xiàn)了該接口。
Interface 的聲明格式:
type interfaceName interface { //方法列表 }
Interface 可以被任意對(duì)象實(shí)現(xiàn),一個(gè)類(lèi)型/對(duì)象也可以實(shí)現(xiàn)多個(gè) interface;
interface的變量可以持有任意實(shí)現(xiàn)該interface類(lèi)型的對(duì)象。
如下面的例子:

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實(shí)現(xiàn)SayHi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human實(shí)現(xiàn)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實(shí)現(xiàn) // 因?yàn)檫@三個(gè)類(lèi)型都實(shí)現(xiàn)了這兩個(gè)方法 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類(lèi)型的變量i var i Men //i能存儲(chǔ)Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存儲(chǔ)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) //這三個(gè)都是不同類(lèi)型的元素,但是他們實(shí)現(xiàn)了interface同一個(gè)接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } }

空接口
空interface(interface{})不包含任何的method,正因?yàn)槿绱耍?span style="background-color: #ffff00;">所有的類(lèi)型都實(shí)現(xiàn)了空interface。空interface對(duì)于描述起不到任何的作用(因?yàn)樗话魏蔚膍ethod),但是空interface在我們需要存儲(chǔ)任意類(lèi)型的數(shù)值的時(shí)候相當(dāng)有用,因?yàn)樗梢源鎯?chǔ)任意類(lèi)型的數(shù)值。它有點(diǎn)類(lèi)似于C語(yǔ)言的void*類(lèi)型。
// 定義a為空接口 var a interface{} var i int = 5 s := "Hello world" // a可以存儲(chǔ)任意類(lèi)型的數(shù)值 a = i a = s
interface的變量里面可以存儲(chǔ)任意類(lèi)型的數(shù)值(該類(lèi)型實(shí)現(xiàn)了interface),那么我們?cè)趺捶聪蛑肋@個(gè)interface變量里面實(shí)際保存了的是哪個(gè)類(lèi)型的對(duì)象呢?目前常用的有兩種方法:switch測(cè)試、Comma-ok斷言。
switch測(cè)試如下:
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斷言的話(huà):
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類(lèi)型可以包含一個(gè)匿名字段,interface也可以嵌套另外一個(gè)接口。
如果一個(gè)interface1作為interface2的一個(gè)嵌入字段,那么interface2隱式的包含了interface1里面的method。
反射
所謂反射(reflect)就是能檢查程序在運(yùn)行時(shí)的狀態(tài)。
使用reflect一般分成三步,下面簡(jiǎn)要的講解一下:要去反射是一個(gè)類(lèi)型的值(這些值都實(shí)現(xiàn)了空interface),首先需要把它轉(zhuǎn)化成reflect對(duì)象(reflect.Type或者reflect.Value,根據(jù)不同的情況調(diào)用不同的函數(shù))。這兩種獲取方式如下:
t := reflect.TypeOf(i) //得到類(lèi)型的元數(shù)據(jù),通過(guò)t我們能獲取類(lèi)型定義里面的所有元素 v := reflect.ValueOf(i) //得到實(shí)際的值,通過(guò)v我們獲取存儲(chǔ)在里面的值,還可以去改變值
轉(zhuǎn)化為reflect對(duì)象之后我們就可以進(jìn)行一些操作了,也就是將reflect對(duì)象轉(zhuǎn)化成相應(yīng)的值,例如
tag := t.Elem().Field(0).Tag //獲取定義在struct里面的標(biāo)簽 name := v.Elem().Field(0).String() //獲取存儲(chǔ)在第一個(gè)字段里面的值
獲取反射值能返回相應(yīng)的類(lèi)型和數(shù)值
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
最后,反射的話(huà),那么反射的字段必須是可修改的,我們前面學(xué)習(xí)過(guò)傳值和傳引用,這個(gè)里面也是一樣的道理。反射的字段必須是可讀寫(xiě)的意思是,如果下面這樣寫(xiě),那么會(huì)發(fā)生錯(cuò)誤
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)
如果要修改相應(yīng)的值,必須這樣寫(xiě)
var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1)
上面只是對(duì)反射的簡(jiǎn)單介紹,更深入的理解還需要自己在編程中不斷的實(shí)踐。
參考文檔:
http://se77en.cc/2014/05/05/methods-interfaces-and-embedded-types-in-golang/
http://se77en.cc/2014/05/04/choose-whether-to-use-a-value-or-pointer-receiver-on-methods/
http://www.cnblogs.com/chenny7/p/4497969.html