一、結構體和方法
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
總而言之:無論是地址還是結構體本身一律使用.
來訪問成員
在上面程式碼中我們就實現了這樣的一棵樹:
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 指標接收者:
- 改變內容必須用指標接收者
- 結構過大也可以考慮指標接收者
- 一致性:如有指標接收者,最好使用指標接收者
- 看下面兩個函式
//值接收者
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)
}
上面這段程式碼就會變成這樣:
所以我們需要將.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)
}
可選擇:
還可以將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)
本作品採用《CC 協議》,轉載必須註明作者和本文連結