「Golang成長之路」物件導向篇

yangkuang發表於2021-06-19

一、結構體和方法

1.Go物件導向的特性

  • Go語言不是純粹的物件導向的語言,準確是描述是,Go語言支援物件導向程式設計的特性.
  • Go語言中沒有傳統的物件導向程式語言的 class ,而Go語言中的 struct 和 其他程式語言中的 class 具有同等地位,也就是說Go語言是 基於 struct 來實現 物件導向 的特性的
  • 物件導向程式設計在Go語言中的支援設計得很具有特點,Go語言放棄了很多物件導向程式設計的概念,如 繼承,過載, 建構函式, 解構函式 ,隱藏this指標等
  • Go語言可以實現物件導向程式設計的特性 封裝 , 繼承, 多型
  • Go語言僅支援封裝,不支援繼承和多型(在go語言中使用介面實現)
  • Go語言物件導向程式設計的支援是語言型別系統 中天然的組成部分,整個型別系統通過介面串聯,靈活度高

2.結構體(struct)
在上面說過:“Go語言中的 struct 和 其他程式語言中的 class 具有同等地位”
對結構體的定義使用如下關鍵字:

type 識別符號 struct{
       field1 type
       field2 type
 }

例如:

// 構建一個二叉樹的結構體
type Node struct{
     Value int
     Left, right *Node
}

那麼怎麼來建立Node呢?

package main

import "fmt"
// 構建一個二叉樹的結構體
type Node struct{
     Value int
     Left, Right *Node
}

func mian(){
    var root Node //建立一個根節點
    //root := Node{} 
    root = Node{Value: 3}
    //root.left = &Node{}  沒有給值時,則預設值為0
    //left和right在結構體中都是指標需要加&
    root.Left = &Node{0, nil, nil}
    root.Right = &Node{5, nil, nil}
    root.Left.Right = &Node{2, nil, nil}
    root.Rigth.Left = new(Node) //先new再賦值
    root.Right.Left = &Node{0, nil, nil}

}
問題一:為什麼這裡指標可以表示為root.Right.Left

答:我們在c/c++中對於指標其實應該這樣寫:root.Right->Left
但是在Go語言中是沒有->這種寫法,它確實就是可以這樣:root.Right.Left
總而言之:無論是地址還是結構體本身一律使用.來訪問成員

在上面程式碼中我們就實現了這樣的一棵樹:
「Golang成長之路」物件導向篇

3.工廠函式:
Go語言中是沒有建構函式的,但是Go語言本身就可以提供構造方法:
如:

root = Node{Value:  0}
root.Left = &Node{}
root.Rigth.Left =  new(Node) 
root.Right.Left =  &Node{4,  nil,  nil}

但是有時候我們想要自己來構造,構造的建立就可以使用工廠函式

//工廠函式(在golang中可以把取地址區域性變數給全域性變數使用)
func greatNode(value int) *Node{
   return &Node{Value:value}
}

其實工廠函式和普通函式沒有本質上的差別

4.為結構定義方法
直接看例子:我們為Node定義一個列印的方法

//(node Node)中node為接收者,print()是為node接收的
func (node Node) print(){
      fmt.Println(node.value)
}

那麼如何呼叫呢?

func mian(){
    var root Node //建立一個根節點
    //root := Node{} 
    root = Node{Value: 3}
    //root.left = &Node{}  沒有給值時,則預設值為0
    //left和right在結構體中都是指標需要加&
    root.Left = &Node{0, nil, nil}
    root.Right = &Node{5, nil, nil}
    root.Left.Right = &Node{2, nil, nil}
    root.Rigth.Left = new(Node) //先new再賦值
    root.Right.Left = &Node{0, nil, nil}
    //呼叫如下:
    root.print()
}

列印值為:3
其實這裡它和普通函式差不多
普通的定義如下:

func print(node Node){
    fmt.Println(noe.value)
}
//呼叫 print(root)

值接收者 VS 指標接收者:

  1. 改變內容必須用指標接收者
  2. 結構過大也可以考慮指標接收者
  3. 一致性:如有指標接收者,最好使用指標接收者
  • 看下面兩個函式
//值接收者
func (node Node) setvalue(value int){
    node.Value = value
}
//指標接收者
func (node *Node) setvalue(value int){
    node.Value = value
}

第一個函式是值傳遞不會真正的修改node.Value的值
第二個函式是指標傳參會真正的改變node.Value的值

  • nil指標也可以呼叫方法

介紹了這些方法,現在來實現一個樹的中序遍歷

func (node *Node) traverse(){
    if node == nil{
        return 
    }
    node.left.traverse()
    fmt.Println(node)
    node.right.traverse()
}

下面來呼叫:

func  mian(){
    var root Node //建立一個根節點  //root := Node{} 
    root = Node{Value:  3}  
    //root.left = &Node{} 沒有給值時,則預設值為0 
    //left和right在結構體中都是指標需要加&
    root.Left =  &Node{0,  nil,  nil}
    root.Right =  &Node{5,  nil,  nil}
    root.Left.Right =  &Node{2,  nil,  nil} 
    root.Rigth.Left =  new(Node)  //先new再賦值
    root.Right.Left =  &Node{0,  nil,  nil}

    root.traverse()

}

輸出為:

0 2 3 0 5

二、包和封裝

1.封裝

  • 名字一般使用CamelCase
  • 首字母大寫:public
  • 首字母小寫:private

2.包
包就是每一個程式中的package

  • 每一個目錄只有一個包
  • main包包含了可執行入口
  • 為機構定義的方法必須放在同一個包內
  • 可以是不同的檔案

下面是一個包:
對於這個包而言它我們要做可執行程式main中使用,就應該將構造體及其方法的首字母寫成大寫

package tree

import "fmt"
//結構體
type Node struct{
   Value int
   Right, Left *Node
}

//工廠函式(在golang中可以把取地址區域性變數給全域性變數使用)
func GreatNode(value int) *Node{
   return &Node{Value:value}
}

func (node *Node)Setvalue(value int){
    node.Value = value
}

//遍歷樹
func (node *Node) Traveser(){
   if node == nil {
      return
  }
   node.Left.Traveser()
   fmt.Println(node.Value)
   node.RightTraveser()
}

在可執行的main中:
我們引入的包名叫:tree
所以需要在構造方法中都新增tree

package mian
import ("awesomeProject/tree"
        "fmt"
    )

func  mian(){
    var root tree.Node //建立一個根節點  //root := tree.Node{} 
    root = tree.Node{Value:  3}  
    //root.left = &Node{} 沒有給值時,則預設值為0 
    //left和right在結構體中都是指標需要加&
    root.Left =  &tree.Node{0,  nil,  nil}
    root.Right =  &tree.Node{5,  nil,  nil}
    root.Left.Right =  &tree.Node{2,  nil,  nil} 
    root.Rigth.Left =  new(tree.Node)  //先new再賦值
    root.Right.Left =  &tree.Node{0,  nil,  nil}

    //呼叫tree中中序遍歷方法
    root.Traveser()
    root.Right.Right = new(tree.Node)

    //呼叫tree中Setvalue()方法
    root.Right.Right.Stevalue(100)
    fmt.Println(root.Right.Right.Value)

三、擴充套件已有型別

如果一個包是別人寫的,那麼我們想要擴充套件它怎麼辦?
在c++及Java裡面我們會使用繼承來擴充套件,相對較麻煩,所以在Go中乾脆就取消了繼承,在Go中擴充套件系統型別及別人的型別有兩種方法:

  • 使用組合
  • 定義別名
  • 使用內嵌

1.使用組合:
tree包:

package tree

import "fmt"
//結構體
type Node struct{
   Value int
   Right, Left *Node
}

//工廠函式(在golang中可以把取地址區域性變數給全域性變數使用)
func GreatNode(value int) *Node{
   return &Node{Value:value}
}

func (node *Node)Setvalue(value int){
    node.Value = value
}

//遍歷樹
func (node *Node) Traveser(){
   if node == nil {
      return
  }
   node.Left.Traveser()
   fmt.Println(node.Value)
   node.RightTraveser()
}

我們在別人寫tree包中使用中序遍歷,那麼我們現在還需要使用後續遍歷,而現在tree包中沒有後序遍歷,此時就需要我們自己來實現

現在我們在main中寫一個結構體:

type mytreeNode struct{
   node *tree.Node  //node為tree中的Node型別
}

程式main:

package main

import (
   "awesomeProject/tree"
 "fmt")

type mytreeNode struct{
   node *tree.Node
}

//有接收者的後序遍歷
func (myNode *mytreeNode)postOrder() {
   if myNode == nil || myNode.node == nil {
      return
  }
//使用組合,我們構建了mytreeNode的結構體,必須使用mytreeNode{}
   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.node)
}

//普通後序遍歷
func postOrder1(myNode *mytreeNode){
   if myNode == nil || myNode.node == nil{
      return
  }
   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   postOrder1(&left)
   postOrder1(&right)
   fmt.Print(myNode.node)

}

func main() {
   var root tree.Node
   root = tree.Node{Value: 0}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}

   fmt.Println()
   //接收者函式
   myRoot := mytreeNode{&root}
   myRoot.postOrder()

   //普通函式
   postOrder1(&mytreeNode{&root})
   //postOrder1(myRoot)
}

最後輸出:

341520
341520

2.使用別名
例如:
現在我們另一個目錄下使用別名的方式來編寫一個佇列(Queue)
同樣,我們將佇列寫在包裡

//定義別名
type Queue []int

完整的queue包:

package queue

type Queue []int

//加入元素
func (q *Queue) Push(v int){
   *q = append(*q, v)
}

//移出首元素
func (q *Queue) Pop() int {
   head := (*q)[0]
   *q = (*q)[1:]
   return head
}

//判斷佇列是否為空
func (q *Queue)IsImpty() bool{
   return len((*q)) == 0
}

在main中呼叫包:

package main

import (
   "awesomeProject/tree/queue"
 "fmt")

func main() {
   q := queue.Queue{1}
   q.Push(2)
   q.Push(3)
   q.Pop()
   fmt.Println(q)
   fmt.Println(q.IsImpty())
   fmt.Println(q.Pop())
   fmt.Println(q.Pop())
   fmt.Println(q.IsImpty())

最後輸出為:

[2 3]
false
2
3
true

3.內嵌(emdedding)
內嵌其實也就是語法糖,它能使我們的程式碼量減少,什麼是內嵌呢?看下面:

type mytreeNode struct{
   node *tree.Node
}
type mytreeNode struct{
   *tree.Node   //Embedding 內嵌
}

很容易看出:內嵌把node給省略了
使用內嵌後:

//後序遍歷
func (myNode *mytreeNode)postOrder() {
   if myNode == nil || myNode.node == nil {
      return
  }

   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.node.Value)
}

上面這段程式碼就會變成這樣:
「Golang成長之路」物件導向篇

所以我們需要將.node改成.Node變成tree.Node後面的.Node; 也可以將.node直接扔掉變成:

/後序遍歷
func (myNode *mytreeNode)postOrder() {
   if myNode == nil {
      return
  }

   left := mytreeNode{myNode.Left}
   right := mytreeNode{myNode.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.Value)
}

可選擇:
「Golang成長之路」物件導向篇
還可以將main中的

func main() {
   var root tree.Node
   root = tree.Node{Value: 0}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}

改成:

func main() {
       //var root tree.Node
       // root = tree.Node{Value: 0}
   root := mytreeNode{&tree.Node{Value: 0}}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}
//這樣就可直接將root值為引數了
   root.postOrder()

   postOrder1(&root)

「Golang成長之路」物件導向篇

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章