Go实现的LinkTable库

lu-lu-lullaby 2022-04-01 12:12:43

目录

 

 

C风格

LinkTable.go实现

简单的单元测试

调用者使用示例

Go风格


C风格

Go语言支持指针。但是为了保障安全性,Go对指针的操作加上各种限制:

  • Go 的指针不能进行数学运算
  • 不同类型的指针不能相互转换
  • 不同类型的指针不能使用 == 或 != 比较
  • 不同类型的指针变量不能相互赋值

想要绕过限制像C语言一样通过指针提升效率,可以借助Go内置的unsafe包

LinkTable.go实现

链表中定义头节点,方便节点的各种操作

// Node 定义链表的节点
type Node struct {
	Next *Node
}

// LinkTable 定义链表
type LinkTable struct {
	head *Node
}
// NewLinkedTable 新建一个空的链表
func NewLinkedTable() *LinkTable {
	return &LinkTable{
		head: new(Node),
	}
}

// GetHead 返回链表的第一个节点
func (lt *LinkTable) GetHead() *Node {
	//在调用者的视角,head是不存在的。因此调用者想要的头节点是第一个节点
	return lt.head.Next
}
// AddHead 在链表头添加节点
func (lt *LinkTable) AddHead(a interface{}) {
	p := (*Node)(unsafe.Pointer(reflect.ValueOf(a).Pointer()))
	p.Next = lt.head.Next
	lt.head.Next = p
}

// DeleteNode 从链表中删除节点(若节点不存在链表,则忽略)
func (lt *LinkTable) DeleteNode(a interface{}) {
	p := (*Node)(unsafe.Pointer(reflect.ValueOf(a).Pointer()))
	pre, cur := lt.head, lt.head.Next
	for cur != nil {
		if cur == p {
			pre.Next = cur.Next
			return
		}
		pre, cur = cur, cur.Next
	}
}

接口interface可以理解为某些方法的集合。一个结构体,只要实现了这些方法(实现了该接口),就属于该集合。

而空接口不包含任何方法,所以任何数据类型,都实现了空接口。

在添加数据时,传入调用者定义的节点a(Next成员必须是结构体的第一个变量)。在LinkTable的内部实现中,通过

reflect.ValueOf()方法获得空接口a内部的Value,然后将其转化为unsafe.Pointer类型,随后就可以进行链表的各种操作。

简单的单元测试

LinkTable_test.go 

Go语言内置一个简易的单元测试框架testing

按照testing的要求编写两个主要函数的单元测试

调用者使用示例

定义自己的节点类型(可包含任意数据)

type MyNode struct {
	Next *MyNode
	Name string
	Data int
}

分别添加节点n1、n2、n3和n4,随后删除头节点和最后一个节点,通过辅助函数showData来查看结果

func main() {
	lt := LinkTable.NewLinkedTable()
	
	n1 := NewNode("n1", 100)
	n2 := NewNode("n2", 200)
	n3 := NewNode("n3", 300)
	n4 := NewNode("n4", 400)
	
	lt.AddHead(n1)
	lt.AddHead(n2)
	lt.AddHead(n3)
	lt.AddHead(n4)
	
	showData(lt)

	lt.DeleteNode(n1)
	showData(lt)
	
	lt.DeleteNode(n4)
	showData(lt)
}
func showData(lt *LinkTable.LinkTable) {
	n := (*MyNode)(unsafe.Pointer(lt.GetHead()))
	fmt.Println("LinkTable: ")
	for n != nil {
		next := ""
		if n.Next != nil {
			next = n.Next.Name
		}
		fmt.Printf("%s: (data: %d, next: %s)\n", n.Name, n.Data, next)
		n = n.Next
	}
	fmt.Println()
}

Go风格

继承是OOP的一大特性。调用者定义的节点类型可以认为是继承了LinkTable中定义的节点类型。而Go中没有继承,只有组合和接口。利用组合,我们可以把我们的数据作为LinkTable中定义的节点的一部分,成为节点的Value。在操作链表时,把只包含数据的Value传给链表,不用理会Next,可以简化调用者的使用逻辑。例如

type MyNode struct {
	Name string
	Data int
}

Go风格的链表实现可以参考Go内置库container/list

// Element is an element of a linked list.
type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *List

	// The value stored with this element.
	Value interface{}
}
具体使用:
	ll := list.New()
	node1 := &Node{name: "n1", data: 100}
	node2 := &Node{name: "n2", data: 200}
	node3 := &Node{name: "n3", data: 300}
	ll.PushFront(node3)
	e2 := ll.PushFront(node2)
	ll.PushFront(node1)

	for e := ll.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}
	fmt.Println()

	ll.Remove(e2)
	for e := ll.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}

输出:

 

https://gitee.com/tornado549/my-linked-table

作者:458

...全文
220 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧