關於面向對象編程你們確定都十分熟悉了,面向對象編程的三個要素就是封裝、繼承和多態。但相對其餘編程語言而言,go語言僅支持封裝,不支持繼承和多態,它沒有class概念,只有struct(結構體),本文主要總結了關於golang中結構體的建立和方法,經過建立一個二叉樹的樹結構並簡單實現其遍歷的方法觀察下在golang中是如何貫徹面向對象編程的理念的。node
二叉樹是每一個結點最多有兩個子樹的樹結構,它由一個一個樹節點組成,每一個節點包含當前節點的值和左右兩個節點的地址,一個個節點鏈接組成一顆完整的樹,它的樹節點結構體類型能夠定義以下:c++
type treeNode struct {
value int
left, right *treeNode
}
複製代碼
golang中能夠經過聲明式語法建立treeNode{value:666,left:nil,right:nil}
(這裏若不指定某一結構體成員的值,該成員的值將默認用零值填充)或者直接將參數按結構體定義成員順序傳入treeNode{5, nil, nil}
(這種初始化形式必需要給全部成員都進行賦值),除此以外還能夠經過new(treeNode)
內建函數進行初始化。golang
須要注意的是用new(T)
內建函數進行初始化時,它返回的是一個分配了零值填充的結構體的內存空間的地址。編程
簡單建立一個樹結構以下述代碼所示:bash
root := treeNode{value: 666}
root.left = &treeNode{value: 1}
root.right = &treeNode{5, nil, nil}
root.left.left = new(treeNode)
root.left.right = &treeNode{888, nil, nil}
複製代碼
這裏須要注意的是root節點的right成員存儲的是地址,但仍然經過.
操做符的形式去訪問其成員,這在go語言當中是可行的。由於go語言中規定:不管是地址仍是結構自己,一概使用.
操做法來訪問成員。編程語言
golang中沒有構造函數的說法,其提供的struct已經知足絕大多數場景下的應用,可是有些時候咱們想要控制結構體的構造可使用自定義的工廠函數返回局部變量的地址,具體代碼以下:函數
func createTreeNode(value int) *treeNode {
return &treeNode{value: value}
}
複製代碼
若是有c++編程經驗的同窗可能會以爲上述代碼有些奇怪,由於c++中局部變量是分配在棧上的,函數退出後就會及時銷燬,而若是要傳出去則須要在堆上分配,而在堆上分配就必需要手動進行釋放,所以c++中是不容許函數中返回局部變量的地址供外部程序進行使用的,而在golang中就不存在該限制。性能
關於這個問題的答案是不須要知道,由於結構是建立在堆上仍是棧上是由go語言的編譯器和它的運行環境決定的。好比說若是上述的工廠函數代碼返回的treeNode
沒有取地址而是直接返回值得話,編譯器極可能就認爲這個變量不須要被外部程序使用,那麼它就會在棧上分配。反之若是treeNode
取得是地址的話,那麼它就會在堆上進行分配,並參與垃圾回收機制。ui
所以咱們須要注意和其餘語言不一樣:go語言中的局部變量不必定在退出函數就銷燬了。this
在結構體定義方法,不是寫在結構體花括號裏面,而是在結構體外面的。假設咱們要給結構體定義一個方法讓其打印當前節點的值爲後續進行遍歷作準備能夠這樣作:
func (node treeNode) print() {
fmt.Println(node.value)
}
複製代碼
咱們能夠發現,結構體方法和普通函數的語法是很是相似的,惟一不一樣的是,結構體的方法在函數名前有一個接收者,意味着這個方法是由指定接收者接收的。go語言當中沒有this指針的概念,而是由接收者來代替this。
其實結構體方法本質上就是函數,咱們能夠把它的接收者當作函數參數的形式,所以結構體方法和下述寫法是等價的:
func print(node treeNode) {
fmt.Println(node.value)
}
複製代碼
這樣當想要調用此方法時,直接將接收者經過參數的形式傳入就能夠了print(root)
,而經過結構體方法須要調用則是經過點操做符的形式root.print()
,能夠看出以上兩種寫法本質上實際上是同樣的,只是調用語法上不一樣。
那麼結構體方法中接收者是按值傳遞仍是按引用地址傳遞呢?
咱們都知道,go語言中函數的參數都是按值傳遞的,既然接收者能夠類比爲函數的參數,那麼同理接收者也是同樣。若是接收者定義爲指針接收者,那麼就會直接傳入調用者的地址,若是定義爲值接收者,就會將調用者的地址解析出來拿到值後拷貝一份再傳入,很是靈活。
接下來分別經過值接收者和指針接收者實現一個給樹節點設置值的方法,觀察下兩者的不一樣: 假設節點原有的值爲666,分別看下調用setValue
方法後兩者的輸出結果。
// 值接收者
func (node treeNode) setValue(val int) {
node.value = val
}
root.setValue(8)
// 輸出:666
複製代碼
// 指針接收者
func (node *treeNode) setValue(val int) {
node.value = val
}
root.setValue(8)
// 輸出:8
複製代碼
由此咱們能夠看出,要想改變結構體內容時就須要使用指針接收者。
那何時該使用值接收者,何時使用指針接收者呢,可概括爲如下幾點:
掌握告終構體方法的定義後,來簡單實現一下二叉樹的前、中、後序遍歷。
首先來看看前序、中序、後序遍歷的特性:
經過上述二叉樹結構初始化代碼後,建立的二叉樹結構以下所示:
// 前序遍歷
func (node *treeNode) traverse() {
if node == nil {
return
}
node.print()
node.left.traverse()
node.right.traverse()
}
// 輸出 666 1 0 888 5
複製代碼
// 中序遍歷
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
// 輸出 0 1 888 666 5
複製代碼
// 後序遍歷
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.right.traverse()
node.print()
}
// 輸出 0 888 1 5 666
複製代碼
這裏須要注意的是:go語言中nil指針也能夠調用方法,也就是說接收者容許空指針,所以咱們不須要在調用方法前判斷調用者是否爲空指針,可是在方法中須要判斷接收者是否爲空指針,若是爲空指針則直接中斷程序。