這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本篇通過實現一個自訂的棧,來學習Go語言的自訂類型及其方法
首先棧的概念不用多說,它是一種支援從頂端插入或刪除的線性表,廢話少說上代碼。
在GOPATH
下建立stack目錄,棧的所有實現在stack.go
檔案之中。
首先需要一個能夠儲存資料的結構體
type Stack []interfice {}
這裡聲明Stack為空白介面類型的切片(Go語言之中的切片可以理解為一個長度可變的數組)。由於Go語言所有類型都實現了空介面,因此任意類型的值都可以儲存在Stack之中。
接下來,由於Stack的底層資料類型是一個切片,我們可以為其實現Len()
和Cap()
方法用來擷取其長度和容量(Go語言之中首字母大寫的方法為包外可訪問的,類似於Java或者C++之中類的public方法)。
func (stack Stack) Len() int { return len(stack)}
方法的定義如上。func
關鍵字用來定義方法,其後緊跟的是方法所作用的類型,這裡這個Len()
方法作用於Stack
這個類型的值,該值通常被稱為該方法的“接受器”(Go語言的型別宣告,變數聲明都將類型放在名稱之後);然後是方法名以及參數列表,這裡參數為空白;最後面是方法的傳回值,這裡為int
。方法的實現調用了Go語言的內建函數len()
,len()
返回切片和數組的長度。
Cap()
方法與Len()
類似,其實現也調用了內建函數cap()
來擷取切片的長度
func (stack Stack) Cap() int { return cap(stack)}
接下來實現其關鍵方法Push()
,Top()
與Pop()
func (stack *Stack) Push(value interface{}) { *stack = append(*stack, value)}
Push()
方法的接收器為一個Stack類型的指標(Go指標的寫法與C/C++類似,類型前面加上*號)。Go語言的所有方法參數都是值傳遞,接收器實際也是作為方法的一個參數傳遞進入方法的。如果傳遞一切片或者數組進方法,實際是將切片或數組的值拷貝了一份傳入了方法之中,此時在方法之中對該切片或數組做的操作都不會影響方法之外的原始值。如果想要方法之中的操作影響到方法外的原始值,則應該使用指標作為參數,對指標的操作會直接反應到記憶體之中的原始值上去。在這裡我們希望更改原始值(往原始的stack
之中添加資料), 所以接收器是一個指標。方法的參數是一個interface{}
類型的值,也就是說該方法可以接受任意類型作為參數。方法的實現使用了內建函數append()
,往切片對尾部中添加新值。
func (stack Stack) Top() (interface{}, error) { if len(stack) == 0 { return nil, errors.New("Out of index, len is 0") } return stack[len(stack) - 1], nil}
Top()
方法返回一個任意類型的值以及一個error
(是的沒錯,Go語言的方法可以返回多個值)。當stack為空白時,返回一個空值和一個error
類型的值(這裡使用errors
包的New()
函數建立)。當stack不為空白時,返回底層切片的最後一個值和一個空的error
。
func (stack *Stack) Pop() (interface{}, error) { theStack := *stack if len(theStack) == 0 { return nil, errors.New("Out of index, len is 0") } value := theStack[len(theStack) - 1] *stack = theStack[:len(theStack) - 1] return value, nil}
Pop()
方法的接收器同樣是一個Stack的指標。其中theStack[:len(theStack) - 1]
這種寫法,是Go中取子切片的方法,:
兩邊是起始index和結束index。起始index為0時可以省略。結束的index也可以省略,省略時結束index為切片的len()
值。
此外還有一個簡單的IsEmpty()
方法
func (stack Stack) IsEmpty() bool { return len(stack) == 0}
以上所有方法實現完畢,寫一個stack_test.go
來測試stack.go
,在包目錄下運行go test即可查看結果。測試通過即可使用go install命令將該stack包安裝到GOPATH
之中,然後就可以在其他項目引用自己實現的Stack
。
完整源碼比較短,直接貼在這裡
stack.go
package stackimport "errors"type Stack []interface {}func (stack Stack) Len() int { return len(stack)}func (stack Stack) IsEmpty() bool { return len(stack) == 0}func (stack Stack) Cap() int { return cap(stack)}func (stack *Stack) Push(value interface{}) { *stack = append(*stack, value)}func (stack Stack) Top() (interface{}, error) { if len(stack) == 0 { return nil, errors.New("Out of index, len is 0") } return stack[len(stack) - 1], nil}func (stack *Stack) Pop() (interface{}, error) { theStack := *stack if len(theStack) == 0 { return nil, errors.New("Out of index, len is 0") } value := theStack[len(theStack) - 1] *stack = theStack[:len(theStack) - 1] return value, nil}
stack_test.go
package stackimport ( "testing")func TestStack_Len(t *testing.T) { var myStack Stack myStack.Push(1) myStack.Push("test") if myStack.Len() == 2 { t.Log("Pass Stack.Len") } else { t.Error("Failed Stack.Len") }}func TestStack_IsEmpty(t *testing.T) { var mStack Stack if mStack.IsEmpty() { t.Log("Pass Stack.IsEmpty") } else { t.Error("Failed Stack.IsEmpty") }}func TestStack_Cap(t *testing.T) { myStack := make(Stack, 3) if myStack.Cap() == 3 { t.Log("Pass Stack.Cap") } else { t.Error("Failed Stack.Cap") }}func TestStack_Push(t *testing.T) { var mStack Stack mStack.Push(3) if mStack.Len() == 1 { t.Log("Pass Stack.Push") } else { t.Error("Failed Stack.Push") }}func TestStack_Top(t *testing.T) { var mStack Stack if _, err := mStack.Top(); err == nil { t.Error("Failed Stack.Top") } mStack.Push(3) if value, _ := mStack.Top(); value == 3 { t.Log("Pass Stack.Top") } else { t.Errorf("Failed Stack.Top, value is %d", value) }}func TestStack_Pop(t *testing.T) { var mStack Stack if _, err := mStack.Pop(); err == nil { t.Error("Failed Stack.Top") } mStack.Push("test") mStack.Push(3) if value, _ := mStack.Pop(); value == 3 && mStack.Len() == 1 { t.Log("Pass Stack.Pop") } else { t.Errorf("Failed Stack.Pop, value is %d, len is %d", value, mStack.Len()) }}
附註
- Go語言的所有內建方法的文檔可以在Go源碼
src/builtin/buildin.go
查閱