本次实验我们使用模块化的思想完成 menu 程序的第二版。
在 Go 语言中要想编写额外的包来供 main 函数调用,我们首先要初始化 .mod 文件,在命令行执行如下命令:
go mod init gitee.com/phony36/menu
执行完成后,在 menu 文件夹中会出现一个名为 go.mod 的文件,其内容如下:
然后在menu 文件夹下新建 linklist 文件夹,并在其中新建 datanode.go 和 linklist.go 文件。完成后,现在的项目结构应该像下面这样:
我们在 datanode.go 文件中定义结构体 DataNode ,它包括命令名称、命令描述、命令对应的 handler 函数和指向下一个 DataNode 结点的指针。注意所有的变量名首字母都要大写,这样我们才能在 linklist 包外访问该 DataNode 和它的成员。datanode.go 文件的代码如下:
package linklist
type DataNode struct {
Cmd string
Desc string
Handler func() int
Next *DataNode
}
在 linklist.go 中实现 FindCmd 和 ShowAllCmd 函数,FindCmd 函数用于找到命令名称为 cmd 的 DataNode 结点并返回指向它的指针;ShowAllCmd 函数遍历整个 DataNode 链表并打印命令名字和命令描述。同样的,这两个函数首字母也要大写才能供外部库调用。linklist.go 文件的代码如下:
package linklist
import "fmt"
func FindCmd(head *DataNode, cmd string) *DataNode {
if head == nil || cmd == "" {
return nil
}
var p *DataNode = head
for p != nil {
if p.Cmd == cmd {
return p
}
p = p.Next
}
return nil
}
func ShowAllCmd(head *DataNode) int {
fmt.Println("Menu List:")
var p *DataNode = head
for p != nil {
fmt.Printf("%-7s - %s\n", p.Cmd, p.Desc)
// fmt.Println("%-7s - %s\n", p.Cmd, p.Desc)
p = p.Next
}
return 0
}
现在开始编写 main.go 文件。
上述编写的两个文件中的函数和数据结构都在包 linklist 下,为了在 main.go 文件中调用它们,我们要导入 linklist 包:
package main
import (
"fmt"
"os"
"gitee.com/phony36/menu/linklist"
)
紧接着定义相关的常量,避免使用幻数 (magic number) 。
const (
CMD_MAX_LEN = 128
DESC_LEN = 1024
CMD_NUM = 10
)
然后定义我们要实现的命令及其相关参数,这些命令组织成一个 DataNode 的链表,并用一个名为 head 的变量指向这个链表的头结点:
var head *linklist.DataNode = &linklist.DataNode {
Cmd: "help",
Desc: "this is help command",
Handler: nil,
Next: &linklist.DataNode {
Cmd: "version",
Desc: "menu program v1.0",
Handler: nil,
Next: &linklist.DataNode {
Cmd: "quit",
Desc: "exit the program",
Handler: quit,
Next: nil,
},
},
}
我们定义了 help 、version 和 quit 三条命令,其中 help 和 quit 都有对应的 handler 函数,它们的实现如下:
var help = func() int {
linklist.ShowAllCmd(head)
return 0
}
var quit = func() int {
fmt.Println("Bye.")
os.Exit(0)
return 0
}
最后在 main 函数中实现接收输入和进行对应处理的逻辑。最终 menu.go 文件应该像下面这样:
package main
import (
"fmt"
"os"
"gitee.com/phony36/menu/linklist"
)
const (
CMD_MAX_LEN = 128
DESC_LEN = 1024
CMD_NUM = 10
)
var head *linklist.DataNode = &linklist.DataNode {
Cmd: "help",
Desc: "this is help command",
Handler: nil,
Next: &linklist.DataNode {
Cmd: "version",
Desc: "menu program v1.0",
Handler: nil,
Next: &linklist.DataNode {
Cmd: "quit",
Desc: "exit the program",
Handler: quit,
Next: nil,
},
},
}
var help = func() int {
linklist.ShowAllCmd(head)
return 0
}
var quit = func() int {
fmt.Println("Bye.")
os.Exit(0)
return 0
}
func main() {
for {
cmd := make([]byte, CMD_MAX_LEN)
fmt.Println(">>> Input a command: ")
fmt.Scanln(&cmd)
p := linklist.FindCmd(head, string(cmd))
if p == nil {
fmt.Println("This is a wrong cmd!")
continue
}
// fmt.Printf("%s - %s\n", p.Cmd, p.Desc)
fmt.Printf("%s - %s\n", p.Cmd, p.Desc)
if p.Cmd == "help" {
help()
}
if p.Handler != nil {
p.Handler()
}
}
}
在执行menu.go时遇到了以上问题,通过查询相关博客发现自己是将$GOPATH
指向了go.mod
所在目录,此时应该在命令行中取消这一环境变量设置:
$ unset GOPATH
为什么将$GOPATH
指向了go.mod
所在目录就不能正常运行程序了呢?
原因如下:如果设置了环境变量$GOPATH
,Go 将默认在$GOPATH/pkg/
中下载和导入依赖库,而go.mod
文件则是将当前工作目录作为项目根目录,从./pkg/
中下载和导入依赖库。因此,当$GOPATH
指向go.mod
所在目录时,就会产生矛盾。
所以解决思路也是显而易见的:应该在执行go mod init
命令后将$GOPATH
也指向了项目目录,所以应该首先选择通过命令行unset GOPATH
。
从Go1.13开始,使用Go Modules管理Go项目,放弃$GOPATH
:
export GO111MODULE=on
;go mod init
解决了上述问题后,程序得以成功运行。
执行下面的命令将其推送到远端仓库:
在gitee中查看: