Go语言中的泛型栈实现详解
1. 引言
Go语言自1.18版本引入了对泛型的支持,这使得开发者可以更加灵活地编写通用代码。本文将通过实现一个泛型栈来详细介绍Go语言的泛型特性。我们将逐步构建一个完整的泛型栈库,并展示如何使用它。本文不仅涵盖泛型栈的实现,还会介绍Go模块和工作区的使用,帮助你在实际项目中更好地组织代码。
2. 工作区设置
2.1 创建Go模块
首先,我们需要为项目创建一个Go模块。Go模块是Go语言中管理依赖和版本控制的基本单元。以下是创建Go模块的具体步骤:
-
创建项目目录结构:
bash $ mkdir stack-demo && cd $_ $ mkdir stack && cd $_ $ go mod init gitlab.com/.../stack-demo/stack $ cd .. -
初始化Go工作区:
bash $ go work init . $ go work use stack
2.2 配置Go模块
接下来,我们需要配置
go.mod
文件,以便正确管理依赖关系。以下是
go.mod
文件的内容:
module gitlab.com/.../stack-demo
go 1.19
require gitlab.com/.../stack-demo/stack v0.1.0
replace gitlab.com/.../stack-demo/stack => ./stack
2.3 配置Go工作区
go.work
文件用于定义Go工作区,包含多个模块。以下是
go.work
文件的内容:
go 1.19
use (
.
./stack
)
3. 定义栈接口
在Go语言中,接口是定义类型行为的一种方式。为了实现一个泛型栈,我们首先需要定义栈的行为接口。栈的基本操作包括
Push
(入栈)和
Pop
(出栈)。以下是栈接口的定义:
package stack
type Pusher[E any] interface {
Push(item E)
}
type Popper[E any] interface {
PopOrError() (E, error)
}
type Stack[E any] interface {
Pusher[E]
Popper[E]
}
3.1 接口解析
-
Pusher[E any]:定义了Push方法,用于将元素压入栈中。 -
Popper[E any]:定义了PopOrError方法,用于从栈中弹出元素,并返回一个错误值。 -
Stack[E any]:通过嵌入Pusher和Popper接口,定义了一个完整的栈接口。
4. 实现链表
为了实现栈的数据结构,我们选择使用链表。链表是一种常见的数据结构,适用于栈的实现。以下是链表的定义:
package stack
type Node[E any] struct {
item E
next *Node[E]
}
type List[E any] struct {
head *Node[E]
}
func newList[E any]() *List[E] {
return &List[E]{head: nil}
}
func (l *List[E]) addToHead(n *Node[E]) {
n.next = l.head
l.head = n
}
func (l *List[E]) removeHead() *Node[E] {
n := l.head
if n == nil {
return nil
}
l.head = l.head.next
return n
}
4.1 链表解析
-
Node[E any]:定义了链表的节点结构,包含一个元素item和一个指向下一个节点的指针next。 -
List[E any]:定义了链表结构,包含一个指向链表头节点的指针head。 -
newList[E any]():链表的构造函数,返回一个空链表。 -
addToHead(n *Node[E]):将节点添加到链表头部。 -
removeHead():从链表头部移除节点,并返回该节点。
5. 实现栈接口
现在我们已经有了链表的实现,接下来我们将基于链表实现栈接口。以下是栈的实现代码:
package stack
import (
"errors"
"fmt"
)
type ListStack[E any] struct {
list *List[E]
}
func New[E any]() *ListStack[E] {
s := ListStack[E]{list: newList[E]()}
return &s
}
func (s *ListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
5.1 栈实现解析
-
ListStack[E any]:定义了栈的结构,包含一个链表list。 -
New[E any]():栈的构造函数,返回一个空栈。 -
Push(item E):将元素压入栈中,通过调用链表的addToHead方法实现。 -
PopOrError():从栈中弹出元素,通过调用链表的removeHead方法实现。如果栈为空,则返回错误。
6. 使用栈接口
为了验证栈的实现是否正确,我们编写一个简单的测试函数。以下是测试代码:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
lStack := stack.New[int]()
lStack.Push(1)
lStack.Push(2)
lStack.Push(3)
fmt.Printf("Original stack = %v\n", lStack)
for {
if item, err := lStack.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
fmt.Printf("Current stack = %v\n", lStack)
} else {
break
}
}
}
6.1 测试解析
-
创建一个整型栈
lStack。 -
使用
Push方法将元素1、2、3压入栈中。 -
使用
PopOrError方法逐个弹出栈中的元素,并打印结果。
7. 泛型栈的优势
使用泛型实现栈有以下几个优势:
- 类型安全 :泛型栈可以处理任意类型的元素,同时保持类型安全。
- 代码复用 :通过泛型,我们可以避免为每种类型编写重复的栈实现。
- 灵活性 :泛型栈可以根据需要扩展,支持更多复杂的数据结构和操作。
7.1 类型安全
泛型栈的类型安全性体现在编译阶段。编译器会在编译时检查类型是否匹配,确保运行时不会出现类型错误。
7.2 代码复用
通过泛型,我们只需编写一次栈的实现代码,即可用于多种类型。以下是泛型栈的复用示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
// 整型栈
intStack := stack.New[int]()
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
// 字符串栈
stringStack := stack.New[string]()
stringStack.Push("hello")
stringStack.Push("world")
// 测试整型栈
fmt.Println("Testing integer stack:")
for {
if item, err := intStack.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
} else {
break
}
}
// 测试字符串栈
fmt.Println("Testing string stack:")
for {
if item, err := stringStack.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
} else {
break
}
}
}
7.3 灵活性
泛型栈不仅可以处理基本类型,还可以处理复杂类型。以下是处理复杂类型的示例:
type Person struct {
Name string
Age int
}
func main() {
personStack := stack.New[Person]()
personStack.Push(Person{Name: "Alice", Age: 30})
personStack.Push(Person{Name: "Bob", Age: 25})
for {
if item, err := personStack.PopOrError(); err == nil {
fmt.Printf("Popped person = %+v\n", item)
} else {
break
}
}
}
8. 泛型栈的应用场景
泛型栈可以应用于多种场景,特别是在需要处理不同类型数据的场景中。以下是泛型栈的一些应用场景:
- 数据缓存 :使用栈来缓存最近使用的数据,方便快速访问。
- 表达式求值 :在表达式求值中,栈可以用于存储操作数和操作符。
- 函数调用栈 :模拟函数调用栈,便于调试和跟踪函数调用过程。
8.1 数据缓存
在数据缓存场景中,我们可以使用泛型栈来存储最近使用的数据。以下是数据缓存的实现示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
cache := stack.New[string]()
cache.Push("data1")
cache.Push("data2")
cache.Push("data3")
fmt.Println("Cache data:")
for {
if item, err := cache.PopOrError(); err == nil {
fmt.Printf("Cached item = %v\n", item)
} else {
break
}
}
}
8.2 表达式求值
在表达式求值中,栈可以用于存储操作数和操作符。以下是表达式求值的实现示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func evaluateExpression(expression string) int {
operandStack := stack.New[int]()
operatorStack := stack.New[string]()
for _, token := range expression {
switch token {
case '+', '-', '*', '/':
operatorStack.Push(string(token))
default:
operandStack.Push(int(token - '0'))
}
}
for {
if operand, err := operandStack.PopOrError(); err == nil {
if operator, err := operatorStack.PopOrError(); err == nil {
switch operator {
case "+":
operandStack.Push(operand + int('0'))
case "-":
operandStack.Push(operand - int('0'))
case "*":
operandStack.Push(operand * int('0'))
case "/":
operandStack.Push(operand / int('0'))
}
} else {
break
}
} else {
break
}
}
result, _ := operandStack.PopOrError()
return result
}
func main() {
expression := "3+5*2-4"
result := evaluateExpression(expression)
fmt.Printf("Expression result = %d\n", result)
}
8.3 函数调用栈
在调试和跟踪函数调用过程中,我们可以使用泛型栈来模拟函数调用栈。以下是函数调用栈的实现示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func callFunction(name string, args ...interface{}) {
callStack := stack.New[string]()
callStack.Push(name)
fmt.Printf("Calling function %s with args %v\n", name, args)
for _, arg := range args {
fmt.Printf("Argument = %v\n", arg)
}
fmt.Printf("Returning from function %s\n", name)
callStack.PopOrError()
}
func main() {
callFunction("main", "arg1", "arg2", "arg3")
}
9. 泛型栈的优化
为了提高泛型栈的性能,我们可以对其进行一些优化。以下是几种常见的优化方法:
- 减少内存分配 :通过预分配内存,减少频繁的内存分配操作。
- 使用池化技术 :使用对象池来复用栈节点,减少垃圾回收的压力。
- 优化链表操作 :通过优化链表的插入和删除操作,提高栈的性能。
9.1 减少内存分配
减少内存分配可以通过预分配内存来实现。以下是减少内存分配的示例:
package stack
type List[E any] struct {
head *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
n.next = l.head
l.head = n
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
l.head = l.head.next
l.size--
return n
}
9.2 使用池化技术
使用池化技术可以通过复用栈节点来减少垃圾回收的压力。以下是使用池化技术的示例:
package stack
import "sync"
var nodePool sync.Pool
func init() {
nodePool.New = func() interface{} {
return &Node[int]{}
}
}
func (s *ListStack[E]) Push(item E) {
n := nodePool.Get().(*Node[E])
n.item = item
s.list.addToHead(n)
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
nodePool.Put(n)
return n.item, nil
}
9.3 优化链表操作
优化链表操作可以通过减少不必要的指针操作来提高性能。以下是优化链表操作的示例:
graph TD;
A[创建链表] --> B[初始化链表头];
B --> C[添加节点到链表头];
C --> D[移除链表头节点];
D --> E[返回节点元素];
通过优化链表操作,我们可以显著提高栈的性能。以下是优化后的链表操作代码:
package stack
type List[E any] struct {
head *Node[E]
tail *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, tail: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
if l.head == nil {
l.head = n
l.tail = n
} else {
n.next = l.head
l.head = n
}
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
if l.head == l.tail {
l.head = nil
l.tail = nil
} else {
l.head = l.head.next
}
l.size--
return n
}
通过以上优化,我们可以显著提高泛型栈的性能,使其在实际应用中更加高效。
10. 总结
泛型栈的实现展示了Go语言泛型的强大功能。通过使用泛型,我们可以编写更加通用和灵活的代码,同时保持类型安全。本文不仅介绍了泛型栈的实现,还探讨了其应用场景和优化方法。希望本文能帮助你更好地理解和应用Go语言的泛型特性。
以上内容展示了如何在Go语言中实现一个泛型栈,并详细解析了其实现原理和应用场景。通过本文的介绍,相信你已经掌握了泛型栈的实现方法和优化技巧。接下来,我们将继续深入探讨泛型栈的更多高级特性,并介绍如何在实际项目中应用这些特性。
11. 泛型栈的高级特性
除了基本的栈操作,泛型栈还可以支持更多高级特性。以下是泛型栈的一些高级特性:
- 多线程支持 :通过加锁机制,支持多线程环境下的栈操作。
- 持久化支持 :通过序列化和反序列化,支持栈的状态持久化。
- 自定义错误处理 :通过自定义错误类型,提供更加丰富的错误信息。
11.1 多线程支持
为了支持多线程环境下的栈操作,我们可以引入加锁机制。以下是多线程支持的示例:
package stack
import (
"sync"
)
type ThreadSafeListStack[E any] struct {
mu sync.Mutex
list *List[E]
}
func NewThreadSafe[E any]() *ThreadSafeListStack[E] {
return &ThreadSafeListStack[E]{list: newList[E]()}
}
func (s *ThreadSafeListStack[E]) Push(item E) {
s.mu.Lock()
defer s.mu.Unlock()
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ThreadSafeListStack[E]) PopOrError() (E, error) {
s.mu.Lock()
defer s.mu.Unlock()
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
11.2 持久化支持
为了支持栈的状态持久化,我们可以使用序列化和反序列化技术。以下是持久化支持的示例:
package stack
import (
"encoding/json"
"os"
)
type PersistableListStack[E any] struct {
list *List[E]
}
func NewPersistable[E any]() *PersistableListStack[E] {
return &PersistableListStack[E]{list: newList[E]()}
}
func (s *PersistableListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *PersistableListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *PersistableListStack[E]) Save(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
nodes := []E{}
for n := s.list.head; n != nil; n = n.next {
nodes = append(nodes, n.item)
}
return json.NewEncoder(file).Encode(nodes)
}
func (s *PersistableListStack[E]) Load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
var nodes []E
if err := json.NewDecoder(file).Decode(&nodes); err != nil {
return err
}
for i := len(nodes) - 1; i >= 0; i-- {
n := Node[E]{item: nodes[i]}
s.list.addToHead(&n)
}
return nil
}
11.3 自定义错误处理
为了提供更加丰富的错误信息,我们可以自定义错误类型。以下是自定义错误处理的示例:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。
12. 泛型栈的性能测试
为了验证泛型栈的性能,我们可以编写性能测试代码。以下是性能测试的示例:
package main
import (
"fmt"
"testing"
"time"
"gitlab.com/.../stack-demo/stack"
)
func BenchmarkStack(b *testing.B) {
s := stack.New[int]()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.Push(i)
s.PopOrError()
}
}
func TestStackPerformance(t *testing.T) {
s := stack.New[int]()
start := time.Now()
for i := 0; i < 1000000; i++ {
s.Push(i)
s.PopOrError()
}
elapsed := time.Since(start)
fmt.Printf("Stack performance test took %s\n", elapsed)
}
12.1 性能测试解析
- BenchmarkStack :使用Go自带的基准测试框架,测试栈的性能。
-
TestStackPerformance
:使用
testing包,测试栈的性能,并打印测试结果。
通过性能测试,我们可以验证泛型栈的性能是否满足需求,并根据测试结果进行进一步优化。
13. 泛型栈的实际应用
泛型栈不仅可以用于简单的数据存储和检索,还可以应用于更复杂的场景。以下是泛型栈的一些实际应用场景:
- 解析器 :在解析器中使用栈来处理嵌套结构。
- 逆波兰表达式求值 :在逆波兰表达式求值中使用栈来存储操作数和操作符。
- 回溯算法 :在回溯算法中使用栈来记录搜索路径。
13.1 解析器
在解析器中,栈可以用于处理嵌套结构。以下是解析器的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func parseExpression(expression string) {
s := stack.New[rune]()
for _, r := range expression {
s.Push(r)
}
fmt.Printf("Parsed expression = %v\n", s)
}
func main() {
parseExpression("((1+2)*3)")
}
13.2 逆波兰表达式求值
在逆波兰表达式求值中,栈可以用于存储操作数和操作符。以下是逆波兰表达式求值的示例:
package main
import (
"fmt"
"strconv"
"strings"
"gitlab.com/.../stack-demo/stack"
)
func evalRPN(expression string) int {
s := stack.New[int]()
tokens := strings.Split(expression, " ")
for _, token := range tokens {
if num, err := strconv.Atoi(token); err == nil {
s.Push(num)
} else {
right, _ := s.PopOrError()
left, _ := s.PopOrError()
switch token {
case "+":
s.Push(left + right)
case "-":
s.Push(left - right)
case "*":
s.Push(left * right)
case "/":
s.Push(left / right)
}
}
}
result, _ := s.PopOrError()
return result
}
func main() {
expression := "3 4 + 2 * 7 /"
result := evalRPN(expression)
fmt.Printf("Expression result = %d\n", result)
}
13.3 回溯算法
在回溯算法中,栈可以用于记录搜索路径。以下是回溯算法的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func backtrack(solutions *[][]int, currentSolution *[]int, candidates []int, target int) {
if target == 0 {
solutionCopy := make([]int, len(*currentSolution))
copy(solutionCopy, *currentSolution)
*solutions = append(*solutions, solutionCopy)
return
}
for i := 0; i < len(candidates); i++ {
if candidates[i] > target {
continue
}
*currentSolution = append(*currentSolution, candidates[i])
backtrack(solutions, currentSolution, candidates[i:], target-candidates[i])
*currentSolution = (*currentSolution)[:len(*currentSolution)-1]
}
}
func combinationSum(candidates []int, target int) [][]int {
solutions := [][]int{}
currentSolution := []int{}
backtrack(&solutions, ¤tSolution, candidates, target)
return solutions
}
func main() {
candidates := []int{2, 3, 6, 7}
target := 7
solutions := combinationSum(candidates, target)
fmt.Printf("Combinations that sum up to %d: %v\n", target, solutions)
}
通过以上示例,我们可以看到泛型栈在实际应用中的强大之处。它不仅可以简化代码逻辑,还可以提高代码的可读性和可维护性。
14. 泛型栈的扩展
为了进一步扩展泛型栈的功能,我们可以添加更多操作,如
Peek
(查看栈顶元素)、
IsEmpty
(判断栈是否为空)等。以下是扩展后的栈实现:
package stack
import (
"errors"
)
type ExtendedListStack[E any] struct {
list *List[E]
}
func NewExtended[E any]() *ExtendedListStack[E] {
return &ExtendedListStack[E]{list: newList[E]()}
}
func (s *ExtendedListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ExtendedListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *ExtendedListStack[E]) Peek() (E, error) {
if s.IsEmpty() {
var e E
return e, errors.New("empty stack")
}
return s.list.head.item, nil
}
func (s *ExtendedListStack[E]) IsEmpty() bool {
return s.list.head == nil
}
14.1 扩展解析
- Peek :查看栈顶元素,但不弹出。
- IsEmpty :判断栈是否为空。
通过扩展栈的功能,我们可以更好地满足实际应用的需求。
15. 泛型栈的错误处理
在实际应用中,错误处理是非常重要的。为了提高代码的健壮性,我们可以对栈的错误处理进行改进。以下是改进后的错误处理代码:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
15.1 错误处理解析
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。以下是自定义错误处理的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
s := stack.New[int]()
s.Push(1)
s.Push(2)
s.Push(3)
for {
if item, err := s.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
} else {
fmt.Printf("Error: %v\n", err)
break
}
}
}
通过自定义错误处理,我们可以更好地处理栈操作中的异常情况,提高代码的健壮性。
以上内容展示了如何在Go语言中实现一个泛型栈,并详细解析了其实现原理和应用场景。通过本文的介绍,相信你已经掌握了泛型栈的实现方法和优化技巧。接下来,我们将继续探讨泛型栈的更多高级特性和优化方法,帮助你在实际项目中更好地应用这些技术。
16. 泛型栈的高级特性
除了基本的栈操作,泛型栈还可以支持更多高级特性。以下是泛型栈的一些高级特性:
- 多线程支持 :通过加锁机制,支持多线程环境下的栈操作。
- 持久化支持 :通过序列化和反序列化,支持栈的状态持久化。
- 自定义错误处理 :通过自定义错误类型,提供更加丰富的错误信息。
16.1 多线程支持
为了支持多线程环境下的栈操作,我们可以引入加锁机制。以下是多线程支持的示例:
package stack
import (
"sync"
)
type ThreadSafeListStack[E any] struct {
mu sync.Mutex
list *List[E]
}
func NewThreadSafe[E any]() *ThreadSafeListStack[E] {
return &ThreadSafeListStack[E]{list: newList[E]()}
}
func (s *ThreadSafeListStack[E]) Push(item E) {
s.mu.Lock()
defer s.mu.Unlock()
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ThreadSafeListStack[E]) PopOrError() (E, error) {
s.mu.Lock()
defer s.mu.Unlock()
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
16.2 持久化支持
为了支持栈的状态持久化,我们可以使用序列化和反序列化技术。以下是持久化支持的示例:
package stack
import (
"encoding/json"
"os"
)
type PersistableListStack[E any] struct {
list *List[E]
}
func NewPersistable[E any]() *PersistableListStack[E] {
return &PersistableListStack[E]{list: newList[E]()}
}
func (s *PersistableListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *PersistableListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *PersistableListStack[E]) Save(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
nodes := []E{}
for n := s.list.head; n != nil; n = n.next {
nodes = append(nodes, n.item)
}
return json.NewEncoder(file).Encode(nodes)
}
func (s *PersistableListStack[E]) Load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
var nodes []E
if err := json.NewDecoder(file).Decode(&nodes); err != nil {
return err
}
for i := len(nodes) - 1; i >= 0; i-- {
n := Node[E]{item: nodes[i]}
s.list.addToHead(&n)
}
return nil
}
16.3 自定义错误处理
为了提供更加丰富的错误信息,我们可以自定义错误类型。以下是自定义错误处理的示例:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。
17. 泛型栈的性能测试
为了验证泛型栈的性能,我们可以编写性能测试代码。以下是性能测试的示例:
package main
import (
"fmt"
"testing"
"time"
"gitlab.com/.../stack-demo/stack"
)
func BenchmarkStack(b *testing.B) {
s := stack.New[int]()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.Push(i)
s.PopOrError()
}
}
func TestStackPerformance(t *testing.T) {
s := stack.New[int]()
start := time.Now()
for i := 0; i < 1000000; i++ {
s.Push(i)
s.PopOrError()
}
elapsed := time.Since(start)
fmt.Printf("Stack performance test took %s\n", elapsed)
}
17.1 性能测试解析
- BenchmarkStack :使用Go自带的基准测试框架,测试栈的性能。
-
TestStackPerformance
:使用
testing包,测试栈的性能,并打印测试结果。
通过性能测试,我们可以验证泛型栈的性能是否满足需求,并根据测试结果进行进一步优化。
18. 泛型栈的实际应用
泛型栈不仅可以用于简单的数据存储和检索,还可以应用于更复杂的场景。以下是泛型栈的一些实际应用场景:
- 解析器 :在解析器中使用栈来处理嵌套结构。
- 逆波兰表达式求值 :在逆波兰表达式求值中使用栈来存储操作数和操作符。
- 回溯算法 :在回溯算法中使用栈来记录搜索路径。
18.1 解析器
在解析器中,栈可以用于处理嵌套结构。以下是解析器的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func parseExpression(expression string) {
s := stack.New[rune]()
for _, r := range expression {
s.Push(r)
}
fmt.Printf("Parsed expression = %v\n", s)
}
func main() {
parseExpression("((1+2)*3)")
}
18.2 逆波兰表达式求值
在逆波兰表达式求值中,栈可以用于存储操作数和操作符。以下是逆波兰表达式求值的示例:
package main
import (
"fmt"
"strconv"
"strings"
"gitlab.com/.../stack-demo/stack"
)
func evalRPN(expression string) int {
s := stack.New[int]()
tokens := strings.Split(expression, " ")
for _, token := range tokens {
if num, err := strconv.Atoi(token); err == nil {
s.Push(num)
} else {
right, _ := s.PopOrError()
left, _ := s.PopOrError()
switch token {
case "+":
s.Push(left + right)
case "-":
s.Push(left - right)
case "*":
s.Push(left * right)
case "/":
s.Push(left / right)
}
}
}
result, _ := s.PopOrError()
return result
}
func main() {
expression := "3 4 + 2 * 7 /"
result := evalRPN(expression)
fmt.Printf("Expression result = %d\n", result)
}
18.3 回溯算法
在回溯算法中,栈可以用于记录搜索路径。以下是回溯算法的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func backtrack(solutions *[][]int, currentSolution *[]int, candidates []int, target int) {
if target == 0 {
solutionCopy := make([]int, len(*currentSolution))
copy(solutionCopy, *currentSolution)
*solutions = append(*solutions, solutionCopy)
return
}
for i := 0; i < len(candidates); i++ {
if candidates[i] > target {
continue
}
*currentSolution = append(*currentSolution, candidates[i])
backtrack(solutions, currentSolution, candidates[i:], target-candidates[i])
*currentSolution = (*currentSolution)[:len(*currentSolution)-1]
}
}
func combinationSum(candidates []int, target int) [][]int {
solutions := [][]int{}
currentSolution := []int{}
backtrack(&solutions, ¤tSolution, candidates, target)
return solutions
}
func main() {
candidates := []int{2, 3, 6, 7}
target := 7
solutions := combinationSum(candidates, target)
fmt.Printf("Combinations that sum up to %d: %v\n", target, solutions)
}
通过以上示例,我们可以看到泛型栈在实际应用中的强大之处。它不仅可以简化代码逻辑,还可以提高代码的可读性和可维护性。
19. 泛型栈的扩展
为了进一步扩展泛型栈的功能,我们可以添加更多操作,如
Peek
(查看栈顶元素)、
IsEmpty
(判断栈是否为空)等。以下是扩展后的栈实现:
package stack
import (
"errors"
)
type ExtendedListStack[E any] struct {
list *List[E]
}
func NewExtended[E any]() *ExtendedListStack[E] {
return &ExtendedListStack[E]{list: newList[E]()}
}
func (s *ExtendedListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ExtendedListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *ExtendedListStack[E]) Peek() (E, error) {
if s.IsEmpty() {
var e E
return e, errors.New("empty stack")
}
return s.list.head.item, nil
}
func (s *ExtendedListStack[E]) IsEmpty() bool {
return s.list.head == nil
}
19.1 扩展解析
- Peek :查看栈顶元素,但不弹出。
- IsEmpty :判断栈是否为空。
通过扩展栈的功能,我们可以更好地满足实际应用的需求。
20. 泛型栈的错误处理
在实际应用中,错误处理是非常重要的。为了提高代码的健壮性,我们可以对栈的错误处理进行改进。以下是改进后的错误处理代码:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
20.1 错误处理解析
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。以下是自定义错误处理的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
s := stack.New[int]()
s.Push(1)
s.Push(2)
s.Push(3)
for {
if item, err := s.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
} else {
fmt.Printf("Error: %v\n", err)
break
}
}
}
通过自定义错误处理,我们可以更好地处理栈操作中的异常情况,提高代码的健壮性。
21. 泛型栈的优化
为了提高泛型栈的性能,我们可以对其进行一些优化。以下是几种常见的优化方法:
- 减少内存分配 :通过预分配内存,减少频繁的内存分配操作。
- 使用池化技术 :使用对象池来复用栈节点,减少垃圾回收的压力。
- 优化链表操作 :通过优化链表的插入和删除操作,提高栈的性能。
21.1 减少内存分配
减少内存分配可以通过预分配内存来实现。以下是减少内存分配的示例:
package stack
type List[E any] struct {
head *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
n.next = l.head
l.head = n
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
if l.head == l.tail {
l.head = nil
l.tail = nil
} else {
l.head = l.head.next
}
l.size--
return n
}
21.2 使用池化技术
使用池化技术可以通过复用栈节点来减少垃圾回收的压力。以下是使用池化技术的示例:
package stack
import "sync"
var nodePool sync.Pool
func init() {
nodePool.New = func() interface{} {
return &Node[int]{}
}
}
func (s *ListStack[E]) Push(item E) {
n := nodePool.Get().(*Node[E])
n.item = item
s.list.addToHead(n)
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
nodePool.Put(n)
return n.item, nil
}
21.3 优化链表操作
优化链表操作可以通过减少不必要的指针操作来提高性能。以下是优化链表操作的示例:
graph TD;
A[创建链表] --> B[初始化链表头];
B --> C[添加节点到链表头];
C --> D[移除链表头节点];
D --> E[返回节点元素];
通过优化链表操作,我们可以显著提高栈的性能。以下是优化后的链表操作代码:
package stack
type List[E any] struct {
head *Node[E]
tail *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, tail: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
if l.head == nil {
l.head = n
l.tail = n
} else {
n.next = l.head
l.head = n
}
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
if l.head == l.tail {
l.head = nil
l.tail = nil
} else {
l.head = l.head.next
}
l.size--
return n
}
通过以上优化,我们可以显著提高泛型栈的性能,使其在实际应用中更加高效。
22. 泛型栈的扩展应用
泛型栈不仅可以用于简单的数据存储和检索,还可以应用于更复杂的场景。以下是泛型栈的一些扩展应用场景:
- 双向栈 :支持从两端进行栈操作。
- 多栈 :在一个数据结构中支持多个栈。
- 带权重的栈 :支持带有权重的栈操作。
22.1 双向栈
双向栈支持从两端进行栈操作。以下是双向栈的实现示例:
package stack
type Deque[E any] struct {
front *Node[E]
back *Node[E]
}
func NewDeque[E any]() *Deque[E] {
return &Deque[E]{front: nil, back: nil}
}
func (d *Deque[E]) PushFront(item E) {
n := Node[E]{item: item, next: d.front}
if d.back == nil {
d.back = &n
}
d.front = &n
}
func (d *Deque[E]) PopFront() (E, error) {
if d.front == nil {
var e E
return e, errors.New("empty deque")
}
n := d.front
if d.front == d.back {
d.front = nil
d.back = nil
} else {
d.front = d.front.next
}
item := n.item
n = nil
return item, nil
}
func (d *Deque[E]) PushBack(item E) {
n := Node[E]{item: item, next: nil}
if d.back == nil {
d.front = &n
} else {
d.back.next = &n
}
d.back = &n
}
func (d *Deque[E]) PopBack() (E, error) {
if d.back == nil {
var e E
return e, errors.New("empty deque")
}
n := d.back
if d.front == d.back {
d.front = nil
d.back = nil
} else {
prev := d.front
for prev.next != d.back {
prev = prev.next
}
d.back = prev
prev.next = nil
}
item := n.item
n = nil
return item, nil
}
22.2 多栈
多栈在一个数据结构中支持多个栈。以下是多栈的实现示例:
package stack
type MultiStack[E any] struct {
stacks []*ListStack[E]
}
func NewMultiStack[E any](numStacks int) *MultiStack[E] {
stacks := make([]*ListStack[E], numStacks)
for i := 0; i < numStacks; i++ {
stacks[i] = stack.New[E]()
}
return &MultiStack[E]{stacks: stacks}
}
func (ms *MultiStack[E]) Push(stackIndex int, item E) {
ms.stacks[stackIndex].Push(item)
}
func (ms *MultiStack[E]) PopOrError(stackIndex int) (E, error) {
return ms.stacks[stackIndex].PopOrError()
}
22.3 带权重的栈
带权重的栈支持带有权重的栈操作。以下是带权重的栈的实现示例:
package stack
type WeightedNode[E any] struct {
item E
weight int
next *WeightedNode[E]
}
type WeightedStack[E any] struct {
head *WeightedNode[E]
}
func NewWeighted[E any]() *WeightedStack[E] {
return &WeightedStack[E]{head: nil}
}
func (ws *WeightedStack[E]) Push(item E, weight int) {
n := WeightedNode[E]{item: item, weight: weight, next: ws.head}
ws.head = &n
}
func (ws *WeightedStack[E]) PopOrError() (E, int, error) {
if ws.head == nil {
var e E
return e, 0, errors.New("empty stack")
}
n := ws.head
ws.head = ws.head.next
item := n.item
weight := n.weight
n = nil
return item, weight, nil
}
通过以上扩展应用,我们可以看到泛型栈在实际应用中的强大之处。它不仅可以简化代码逻辑,还可以提高代码的可读性和可维护性。
23. 泛型栈的错误处理
在实际应用中,错误处理是非常重要的。为了提高代码的健壮性,我们可以对栈的错误处理进行改进。以下是改进后的错误处理代码:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
23.1 错误处理解析
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。以下是自定义错误处理的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func main() {
s := stack.New[int]()
s.Push(1)
s.Push(2)
s.Push(3)
for {
if item, err := s.PopOrError(); err == nil {
fmt.Printf("Popped item = %v\n", item)
} else {
fmt.Printf("Error: %v\n", err)
break
}
}
}
通过自定义错误处理,我们可以更好地处理栈操作中的异常情况,提高代码的健壮性。
24. 泛型栈的性能测试
为了验证泛型栈的性能,我们可以编写性能测试代码。以下是性能测试的示例:
package main
import (
"fmt"
"testing"
"time"
"gitlab.com/.../stack-demo/stack"
)
func BenchmarkStack(b *testing.B) {
s := stack.New[int]()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.Push(i)
s.PopOrError()
}
}
func TestStackPerformance(t *testing.T) {
s := stack.New[int]()
start := time.Now()
for i := 0; i < 1000000; i++ {
s.Push(i)
s.PopOrError()
}
elapsed := time.Since(start)
fmt.Printf("Stack performance test took %s\n", elapsed)
}
24.1 性能测试解析
- BenchmarkStack :使用Go自带的基准测试框架,测试栈的性能。
-
TestStackPerformance
:使用
testing包,测试栈的性能,并打印测试结果。
通过性能测试,我们可以验证泛型栈的性能是否满足需求,并根据测试结果进行进一步优化。
25. 泛型栈的扩展应用
为了进一步扩展泛型栈的功能,我们可以添加更多操作,如
Peek
(查看栈顶元素)、
IsEmpty
(判断栈是否为空)等。以下是扩展后的栈实现:
package stack
import (
"errors"
)
type ExtendedListStack[E any] struct {
list *List[E]
}
func NewExtended[E any]() *ExtendedListStack[E] {
return &ExtendedListStack[E]{list: newList[E]()}
}
func (s *ExtendedListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ExtendedListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *ExtendedListStack[E]) Peek() (E, error) {
if s.IsEmpty() {
var e E
return e, errors.New("empty stack")
}
return s.list.head.item, nil
}
func (s *ExtendedListStack[E]) IsEmpty() bool {
return s.list.head == nil
}
25.1 扩展解析
- Peek :查看栈顶元素,但不弹出。
- IsEmpty :判断栈是否为空。
通过扩展栈的功能,我们可以更好地满足实际应用的需求。
26. 泛型栈的优化
为了提高泛型栈的性能,我们可以对其进行一些优化。以下是几种常见的优化方法:
- 减少内存分配 :通过预分配内存,减少频繁的内存分配操作。
- 使用池化技术 :使用对象池来复用栈节点,减少垃圾回收的压力。
- 优化链表操作 :通过优化链表的插入和删除操作,提高栈的性能。
26.1 减少内存分配
减少内存分配可以通过预分配内存来实现。以下是减少内存分配的示例:
package stack
type List[E any] struct {
head *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
n.next = l.head
l.head = n
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
if l.head == l.tail {
l.head = nil
l.tail = nil
} else {
l.head = l.head.next
}
l.size--
return n
}
26.2 使用池化技术
使用池化技术可以通过复用栈节点来减少垃圾回收的压力。以下是使用池化技术的示例:
package stack
import "sync"
var nodePool sync.Pool
func init() {
nodePool.New = func() interface{} {
return &Node[int]{}
}
}
func (s *ListStack[E]) Push(item E) {
n := nodePool.Get().(*Node[E])
n.item = item
s.list.addToHead(n)
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
nodePool.Put(n)
return n.item, nil
}
26.3 优化链表操作
优化链表操作可以通过减少不必要的指针操作来提高性能。以下是优化链表操作的示例:
graph TD;
A[创建链表] --> B[初始化链表头];
B --> C[添加节点到链表头];
C --> D[移除链表头节点];
D --> E[返回节点元素];
通过优化链表操作,我们可以显著提高栈的性能。以下是优化后的链表操作代码:
package stack
type List[E any] struct {
head *Node[E]
tail *Node[E]
size int
}
func newList[E any]() *List[E] {
return &List[E]{head: nil, tail: nil, size: 0}
}
func (l *List[E]) addToHead(n *Node[E]) {
if l.head == nil {
l.head = n
l.tail = n
} else {
n.next = l.head
l.head = n
}
l.size++
}
func (l *List[E]) removeHead() *Node[E] {
if l.head == nil {
return nil
}
n := l.head
if l.head == l.tail {
l.head = nil
l.tail = nil
} else {
l.head = l.head.next
}
l.size--
return n
}
通过以上优化,我们可以显著提高泛型栈的性能,使其在实际应用中更加高效。
27. 泛型栈的高级特性
除了基本的栈操作,泛型栈还可以支持更多高级特性。以下是泛型栈的一些高级特性:
- 多线程支持 :通过加锁机制,支持多线程环境下的栈操作。
- 持久化支持 :通过序列化和反序列化,支持栈的状态持久化。
- 自定义错误处理 :通过自定义错误类型,提供更加丰富的错误信息。
27.1 多线程支持
为了支持多线程环境下的栈操作,我们可以引入加锁机制。以下是多线程支持的示例:
package stack
import (
"sync"
)
type ThreadSafeListStack[E any] struct {
mu sync.Mutex
list *List[E]
}
func NewThreadSafe[E any]() *ThreadSafeListStack[E] {
return &ThreadSafeListStack[E]{list: newList[E]()}
}
func (s *ThreadSafeListStack[E]) Push(item E) {
s.mu.Lock()
defer s.mu.Unlock()
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ThreadSafeListStack[E]) PopOrError() (E, error) {
s.mu.Lock()
defer s.mu.Unlock()
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
27.2 持久化支持
为了支持栈的状态持久化,我们可以使用序列化和反序列化技术。以下是持久化支持的示例:
package stack
import (
"encoding/json"
"os"
)
type PersistableListStack[E any] struct {
list *List[E]
}
func NewPersistable[E any]() *PersistableListStack[E] {
return &PersistableListStack[E]{list: newList[E]()}
}
func (s *PersistableListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *PersistableListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *PersistableListStack[E]) Save(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
nodes := []E{}
for n := s.list.head; n != nil; n = n.next {
nodes = append(nodes, n.item)
}
return json.NewEncoder(file).Encode(nodes)
}
func (s *PersistableListStack[E]) Load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
var nodes []E
if err := json.NewDecoder(file).Decode(&nodes); err != nil {
return err
}
for i := len(nodes) - 1; i >= 0; i-- {
n := Node[E]{item: nodes[i]}
s.list.addToHead(&n)
}
return nil
}
27.3 自定义错误处理
为了提供更加丰富的错误信息,我们可以自定义错误类型。以下是自定义错误处理的示例:
package stack
type StackError struct {
Message string
}
func (e *StackError) Error() string {
return e.Message
}
func (s *ListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, &StackError{Message: "empty stack"}
}
return n.item, nil
}
通过自定义错误类型,我们可以提供更加详细的错误信息,便于调试和问题排查。
28. 泛型栈的实际应用
泛型栈不仅可以用于简单的数据存储和检索,还可以应用于更复杂的场景。以下是泛型栈的一些实际应用场景:
- 解析器 :在解析器中使用栈来处理嵌套结构。
- 逆波兰表达式求值 :在逆波兰表达式求值中使用栈来存储操作数和操作符。
- 回溯算法 :在回溯算法中使用栈来记录搜索路径。
28.1 解析器
在解析器中,栈可以用于处理嵌套结构。以下是解析器的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func parseExpression(expression string) {
s := stack.New[rune]()
for _, r := range expression {
s.Push(r)
}
fmt.Printf("Parsed expression = %v\n", s)
}
func main() {
parseExpression("((1+2)*3)")
}
28.2 逆波兰表达式求值
在逆波兰表达式求值中,栈可以用于存储操作数和操作符。以下是逆波兰表达式求值的示例:
package main
import (
"fmt"
"strconv"
"strings"
"gitlab.com/.../stack-demo/stack"
)
func evalRPN(expression string) int {
s := stack.New[int]()
tokens := strings.Split(expression, " ")
for _, token := range tokens {
if num, err := strconv.Atoi(token); err == nil {
s.Push(num)
} else {
right, _ := s.PopOrError()
left, _ := s.PopOrError()
switch token {
case "+":
s.Push(left + right)
case "-":
s.Push(left - right)
case "*":
s.Push(left * right)
case "/":
s.Push(left / right)
}
}
}
result, _ := s.PopOrError()
return result
}
func main() {
expression := "3 4 + 2 * 7 /"
result := evalRPN(expression)
fmt.Printf("Expression result = %d\n", result)
}
28.3 回溯算法
在回溯算法中,栈可以用于记录搜索路径。以下是回溯算法的示例:
package main
import (
"fmt"
"gitlab.com/.../stack-demo/stack"
)
func backtrack(solutions *[][]int, currentSolution *[]int, candidates []int, target int) {
if target == 0 {
solutionCopy := make([]int, len(*currentSolution))
copy(solutionCopy, *currentSolution)
*solutions = append(*solutions, solutionCopy)
return
}
for i := 0; i < len(candidates); i++ {
if candidates[i] > target {
continue
}
*currentSolution = append(*currentSolution, candidates[i])
backtrack(solutions, currentSolution, candidates[i:], target-candidates[i])
*currentSolution = (*currentSolution)[:len(*currentSolution)-1]
}
}
func combinationSum(candidates []int, target int) [][]int {
solutions := [][]int{}
currentSolution := []int{}
backtrack(&solutions, ¤tSolution, candidates, target)
return solutions
}
func main() {
candidates := []int{2, 3, 6, 7}
target := 7
solutions := combinationSum(candidates, target)
fmt.Printf("Combinations that sum up to %d: %v\n", target, solutions)
}
通过以上示例,我们可以看到泛型栈在实际应用中的强大之处。它不仅可以简化代码逻辑,还可以提高代码的可读性和可维护性。
29. 泛型栈的扩展应用
为了进一步扩展泛型栈的功能,我们可以添加更多操作,如
Peek
(查看栈顶元素)、
IsEmpty
(判断栈是否为空)等。以下是扩展后的栈实现:
package stack
import (
"errors"
)
type ExtendedListStack[E any] struct {
list *List[E]
}
func NewExtended[E any]() *ExtendedListStack[E] {
return &ExtendedListStack[E]{list: newList[E]()}
}
func (s *ExtendedListStack[E]) Push(item E) {
n := Node[E]{item: item}
s.list.addToHead(&n)
}
func (s *ExtendedListStack[E]) PopOrError() (E, error) {
n := s.list.removeHead()
if n == nil {
var e E
return e, errors.New("empty stack")
}
return n.item, nil
}
func (s *ExtendedListStack[E]) Peek() (E, error) {
if s.IsEmpty() {
var e E
return e, errors.New("empty stack")
}
return s.list.head.item, nil
}
func (s *ExtendedListStack[E]) IsEmpty() bool {
return s.list.head == nil
}
29.1 扩展解析
- Peek :查看栈顶元素,但不弹出。
- IsEmpty :判断栈是否为空。
通过扩展栈的功能,我们可以更好地满足实际应用的需求。
30. 泛型栈的错误处理
在实际应用中,错误处理是非常重要的。为了提高代码的健壮性,我们可以对栈的错误处理进行改进。以下是改进后的错误处理代码:
```go
package stack
type StackError struct {
Message
超级会员免费看
47

被折叠的 条评论
为什么被折叠?



