1. 程式人生 > 其它 >Go型別巢狀時容易犯的錯誤

Go型別巢狀時容易犯的錯誤

技術標籤:程式語言go

記錄一個在go程式設計容易犯的錯誤。下面是用陣列實現棧的程式碼:

這部分程式碼是實現了一個數組,支援常見的插入元素、刪除元素等基本操作。

// 實現支援泛型的陣列
package array

import (
	"bytes"
	"fmt"
	"reflect"
)

type MyArray struct {
	arr  []interface{}
	size int
}

// 建立一個新的陣列, 切片長度設定為capacity, 相當於定長陣列
func NewArray(capacity int) *
MyArray { return &MyArray{ arr: make([]interface{}, capacity), size: 0, } } // 獲取陣列容量 func (m *MyArray) GetCapacity() int { return len(m.arr) } // 獲取陣列中元素的個數 func (m *MyArray) GetSize() int { return m.size } // 判斷陣列是否為空 func (m *MyArray) IsEmpty() bool { return m.size == 0 } // 向陣列末尾位置新增一個元素
func (m *MyArray) AddLast(elem interface{}) { m.AddIndex(elem, m.size) } // 在陣列第index個位置新增一個元素, index 0 ~ size func (m *MyArray) AddIndex(elem interface{}, index int) { if index < 0 || index > m.size { panic("Illegal parameter index!") } if m.GetCapacity() == m.GetSize() { m.resize
(m.GetSize() * 2) } for i := m.size - 1; i >= index; i-- { m.arr[i+1] = m.arr[i] } m.arr[index] = elem m.size++ } func (m *MyArray) resize(newCapacity int) { newArr := make([]interface{}, newCapacity) for i := 0; i < m.GetSize(); i++ { newArr[i] = m.arr[i] } m.arr = newArr } // 在陣列開始位置新增一個元素 func (m *MyArray) AddFirst(elem interface{}) { m.AddIndex(elem, 0) } // 獲取陣列指定位置的元素,index範圍為[0,size) func (m *MyArray) Get(index int) interface{} { if index < 0 || index >= m.size { panic("Illegal index value!") } return m.arr[index] } // 修改陣列指定位置的元素 func (m *MyArray) Set(index int, value interface{}) { if index < 0 || index >= m.size { panic("Illegal index value!") } // add comments m.arr[index] = value } // 查詢陣列中是否包含指定的元素 待修改 func (m *MyArray) Contains(e interface{}) bool { for i := 0; i < m.size; i++ { if reflect.DeepEqual(m.arr[i], e) { return true } } return false } // 查詢陣列中指定元素的索引,如果不存在元素則返回-1,待修改 func (m *MyArray) Find(e interface{}) int { for i := 0; i < m.size; i++ { if reflect.DeepEqual(m.arr[i], e) { return i } } return -1 } // 刪除陣列中指定位置的元素, 位置只能是0 - (size-1)之間的元素, 返回刪除的元素 // 刪除一個元素之後,如果陣列元素為容量的四分之一就縮容到原來的一半 func (m *MyArray) DeleteIndex(index int) (ret interface{}) { if index < 0 || index >= m.size { panic("DeleteIndex Fail: Illegal index value!") } ret = m.arr[index] for i := index + 1; i < m.size; i++ { m.arr[i-1] = m.arr[i] } m.size-- if m.size == m.GetCapacity()/4 && m.GetCapacity()/2 != 0 { m.resize(m.GetCapacity() / 2) } return } // 刪除陣列首個元素 func (m *MyArray) DeleteFirst() (ret interface{}) { ret = m.DeleteIndex(0) return } // 刪除陣列末尾元素 func (m *MyArray) DeleteLast() (ret interface{}) { ret = m.DeleteIndex(m.size - 1) return } // 將陣列以字串的形式列印 func (m *MyArray) ArrayToString() string { var buf bytes.Buffer if size := m.GetSize(); size == 0 { buf.WriteString("[]") } else { buf.WriteString("[") for i := 0; i < size; i++ { if i == size-1 { buf.WriteString(fmt.Sprintf("%v", m.arr[i]) + "]") } else { buf.WriteString(fmt.Sprintf("%v", m.arr[i]) + " ") } } } return buf.String() }

接下來的程式碼是基於上面實現的陣列封裝成一個棧,通過型別巢狀。這個棧支援常見的壓棧、出棧、獲取棧中元素數目以及訪問棧頂元素等常規操作。

package arrayStack

import (
	"bytes"
	"fmt"
	"github.com/algo/DataStructureByGo/02Stacks-and-Queues/01Stack/array"
)

type ArrayStack struct {
	*array.MyArray
}

func NewArrayStack(capacity int) *ArrayStack {
	return &ArrayStack{
		array.NewArray(capacity),
	}
}

// 獲取棧中元素數目
func (as *ArrayStack) GetSize() int {
	return as.GetSize()
}

// 判斷棧是否為空
func (as *ArrayStack) IsEmpty() bool {
	return as.IsEmpty()
}

// 壓棧, 底層陣列能夠自動擴縮容
func (as *ArrayStack) Push(e interface{}) {
	as.AddLast(e)
}

// 出棧並返回棧頂元素
func (as *ArrayStack) Pop() interface{} {
	return as.DeleteLast()
}

// 返回棧頂元素
func (as *ArrayStack) Peek() interface{} {
	return as.Get(as.GetSize() - 1)
}

// 將棧以字串的形式打印出來
func (as *ArrayStack) ArrayStackToString() string {
	var buf bytes.Buffer
	buf.WriteString("Stack:")
	if as.GetSize() == 0 {
		buf.WriteString("[] top")
	} else {
		buf.WriteString("[")
		for i := 0; i < as.GetSize(); i++ {
			buf.WriteString(fmt.Sprintf("%v", as.Get(i)))
			if i != as.GetSize()-1 {
				buf.WriteString(" ,")
			}
		}
		buf.WriteString("] top")
	}
	return buf.String()
}

接下來測試這個arrayStack,在main函式中有如下程式碼:

func main() {
	// var stack stack.Stack
	stack := arrayStack.NewArrayStack(10)
	fmt.Println(stack.ArrayStackToString())
	for i := 0; i < 10; i++ {
		stack.Push(i)
	}
	fmt.Println(stack.ArrayStackToString())
	stack.Pop()
	fmt.Println(stack.ArrayStackToString())
}

經過除錯發現,執行到第4行進入到ArrayStackToString()方法,並執行到if as.GetSize()==0時會報棧溢位錯誤。

image-20210203185424374

這是怎麼回事呢?

關鍵:

通過嵌入型別,與內部型別相關的識別符號會提升到外部型別上,這些被提升的識別符號就像直接宣告在外部型別裡的識別符號一樣,也是外部型別的一部分,這樣外部型別就組合了內部型別包含的所有屬性,且可以新增新的欄位和方法。外部型別也可以通過宣告與內部型別識別符號同名的識別符號來覆蓋內部標誌符的欄位或方法。(From 《Go語言實戰》)

原來是結構體ArrayStack的方法GetSize與結構體嵌入型別array.MyArray的方法同名,外部型別的方法覆蓋了內部型別的方法,那麼當呼叫到as.GetSize()方法時,就會不斷遞迴呼叫自己,最後出現棧不夠用溢位的情況。

func (as *ArrayStack) GetSize() int {
	return as.GetSize()
}

那麼如何修改呢?只需要將外部型別中與內部型別方法重名的方法修改一下名稱即可。

關於型別巢狀,忘了翻一下《Go語言實戰》;方法集的內容參考一下這個部落格,講的蠻好!