571
社区成员
发帖
与我相关
我的任务
分享目录
Go语言支持指针。但是为了保障安全性,Go对指针的操作加上各种限制:
想要绕过限制像C语言一样通过指针提升效率,可以借助Go内置的unsafe包
链表中定义头节点,方便节点的各种操作
// 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()
}

继承是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