文章每週持續更新,原創不易,「三連」讓更多人看到是對我最大的確定。能夠微信搜索公衆號「 後端技術學堂 」第一時間閱讀(通常比博客早更新一到兩篇)程序員
對於通常的語言使用者來講 ,20% 的語言特性就可以知足 80% 的使用需求,剩下在使用中掌握。基於這一理論,Go 基礎系列的文章不會刻意追求面面俱到,但該有知識點都會覆蓋,目的是帶你快跑遇上 Golang 這趟新車。編程
最近工做上和生活上的事情都不少,這篇文章計劃是週末發的,可是週末太忙時間不夠,同時爲了保證文章質量,反覆修改到如今纔算完成。後端
有時候仍是很想回到學校,一心只用讀書睡覺打遊戲的日子,成年人的世界老是被各類中斷。不過,不用擔憂 lemon 能處理好,答應你們要寫完的 Go 基礎系列可能會遲到,但不會缺席。微信
今天咱們來繼續學習,Go 中的面向對象編程思想,包括 方法 和 接口 兩大部分學習內容。函數
經過學習本文,你將瞭解:學習
若是你使用 C++ 或 Java 這類面向對象的語言,確定知道類 class
和方法 method
的概念,Golang 中沒有class
關鍵字,但有上節介紹的 struct
結構體提供相似功能,配合方法和接口的支持,完成面向對象的程序設計徹底沒有問題,下面咱們就來學習下方法和接口。設計
方法就是一類帶特殊的接收者參數的函數 ,這些特殊的參數能夠是結構體也能夠是結構體指針,但不能是內置類型。指針
爲了便於說明,先來定義一個結構體 Person
包含name
和 age
屬性。code
type Person struct { name string age int }
下面給 Person
定義兩個方法,分別用於獲取name
和age
,重點看下代碼中方法的定義語法。對象
func (p Person) GetName() string { return p.name + "'s age is" } func (p Person) GetAge() int { return p.age }
看了上面的方法定義是否是以爲和函數定義有點相似,還記得函數的定義嗎?爲了喚起你的記憶,下面分別定義兩個相同功能的函數,你們能夠對比一下。
func GetNameF(p Person) string { return p.name + "'s age is" } func GetNameF(p Person) int { return p.age }
除了定義上的區別,還有調用上的區別。下面示例代碼演示了兩種調用方式的不一樣,在fmt.Println
中前面 2 個是正常函數調用,後面 2 個是方法調用,就是用點號.
和括號()
的區別。
p := Person{"lemon", 18} fmt.Println(GetNameF(p), GetNameF(p), p.GetName(), p.GetAge()) //輸出 lemon's age is 18 lemon's age is 18
上面我演示的方法 GetName
和GetAge
的接收者是Person
值,這種值傳遞方式是沒辦法修改接收者內部狀態的,好比你無法經過方法調用修改 Person
的 name
或age
。
假設有個需求要修改用戶年齡,咱們像下面這樣定義方法 ageWriteable
,調用該方法以後 p
的 name
屬性並不會變化。
func (p *Person) ageWriteable() int { p.age += 10 return p.age }
那要怎麼才能實現對 p
的修改呢? 沒錯用 *Person
指針類型便可實現修改。類比 C++
中用指針或引用來理解。
func (p *Person) ageWriteable() int { p.age += 10 return p.age }
Golang 很是的聰明,爲了避免讓你麻煩,它能自動識別方法的實際接收者類型(指針或值),並默默的幫你作轉換,以便「方法」能正確的工做。
仍是用咱們上面定義的方法舉例,先來看以「值」做爲接收者的方法調用。方便閱讀,我把前面的定義再寫一遍。
func (p Person) GetName() string { return p.name + "'s age is" }
對於這個定義的方法,按下面的調用方式 p
和 pp
都能調用 GetName
方法。
怎麼作到的呢?原來 pp
在調用方法時 Go 默默的作了隱式的轉換,實際上是按照 (*pp).GetName*()
去調用方法,怎麼實現轉換的這點咱們不用關心,先用起來就能夠。
p := Person{"lemon", 18} pp := &Person{"lemon", 18} fmt.Println(p.GetName(), pp.GetName()) // p 和 pp都能調用 GetName 方法
同理,對接收者是指針的方法,也能夠按給它傳遞值的方式來調用,這裏再也不贅述。
對方法的說明,就簡單介紹到這裏,更多細節不去深究,留給你們在使用中學習。
接口我想不到準確的描述語句來講明他,通俗來說接口類型就是一類預先約定好的方法聲明集合。
接口定義就是把一系列可能實現的方法先聲明出來,後面只要哪一個類型徹底實現了某個接口聲明的方法,就可用這個「接口變量」來保存這些方法的值,實際上是抽象設計的概念。
能夠類比 C++
中的純虛函數。
爲了說明接口如何定義,咱們要作一些準備工做。
name
和 age
type man struct { name string age int } type woman struct { name string age int }
getName
和 getAge
用於獲取各自的姓名和年齡。func (m *man) getName() string { return m.name } func (m *woman) getName() string { return m.name } func (m *man) getAge() int { return m.age } func (m *woman) getAge() int { return m.age }
好了, 下面咱們的主角「接口」登場, 咱們來實現一個通用的 humanIf
接口類型,這個接口包含了 getName()
方法聲明,注意接口包含的這個方法的聲明樣式,和前面咱們定義的 man
與 women
的 getName
方法一致。同理 getAge()
樣式也一致。
type humanIf interface { getName() string getAge() int }
如今可使用這個接口了!無論男人女人反正都是人,是人就能夠用個人 humanIf
接口獲取姓名。
var m humanIf = &man{"lemon", 18} var w humanIf = &woman{"hanmeimei", 19} fmt.Println(m.getName(), w.getName())
當給定一個接口值,咱們如何知道他表明的底層值的具體類型呢?仍是上面的例子,咱們拿到了 humanIf
類型的變量 m
和 w
, 怎麼才能知道它們究竟是 man
仍是 women
類型呢?
有兩種方法能夠肯定變量 m
和 w
的底層值類型。
斷言若是不是預期的類型,就會拋出 panic
異常,程序終止。
若是斷言是符合預期的類型,會把調用者實際的底層值返回。
v0 := w.(man) // w保存的不是 man 類型,程序終止 v1 := m.(man) // m保存的符合 man 類型,v1被賦值 m 的底層值 v, right := a.(man) // 兩個返回值,第一個是值,第二表明是否斷言正確的布爾值 fmt.Println(v, right)
相比類型斷言直接粗暴的讓程序終止,「類型選擇」語法更加的溫和,即便類型不符合也不會讓程序掛掉。
下面示例,v3
得到 w
的底層類型,在後面 case
經過類型比較打印出匹配的類型。注意:type
也是關鍵字。
switch v3 := w.(type) { case man: fmt.Println("it is type:man", v3) case women: fmt.Println("it is type:women", v3) default: fmt.Printf("unknow type:%T value:%v", v3, v3) }
空接口 interface{}
表明包含了 0 個方法的接口,試想一下每一個類型都至少實現了零個方法,因此任何類型均可以給空接口類型賦值。
下面示例,用 man
值給空接口賦值。
type nilIf interface{} var ap nilIf = &man{"lemon", 18} //等價定義 var ap interface{} = &man{"lemon", 18} //等價於上面一句
空接口能夠接收任何類型的值,包括指針、值甚至是nil
值。
// 接收指針 var ap nilIf = &man{"lemon", 18} fmt.Println("interface", ap) // 接收值 var a nilIf = man{"lemon", 18} fmt.Println("interface", a) // 接收nil值 var b nilIf fmt.Println("interface", b)
對 C 或 C++ 程序員來講空指針是噩夢,若是對空指針作操做,結果是不可預知的,很大機率會致使程序崩潰,程序莫名其妙掛掉,想一想就使人頭禿。
Golang
中處理空指針這種狀況要優雅的多,容許用空底層值調用接口,可是要修改方法定義,正確處理 nil
值避免程序崩潰。
func (m *man) getName() string { if m == nil { return "nil" } return m.name }
下面演示了使用處理了 nil
值的方法,雖然 nilMan
是空指針,但仍然能夠調用 getName
方法。
var nilMan *man // 定義了一個空指針 nilMan var w humanIf = nilMan fmt.Println(w.getName())
可是,若是接口自己是 nil
去調用方法,仍然會引起異常。
manIf = nil fmt.Println("interface", manIf.getName())
本節學習的接口和方法是 Golang
對面向對象程序設計的支持,能夠看到實現的很是簡潔,並沒經常使用的面嚮對象語言那麼複雜的語法和關鍵字,簡單不表明不夠好,實際上也基本夠用,一句話歸納就是簡潔並不簡單。
感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,咱們一塊兒在探討中學習。
今天的技術分享就到這裏,咱們下期再見。
創做不易,白票不是好習慣,若是在我這有收穫,動動手指「點贊」「關注」是對我持續創做的最大支持。
能夠微信搜索公衆號「 後端技術學堂 」回覆「資料」「1024」有我給你準備的各類編程學習資料。文章每週持續更新,咱們下期見!