首页 > 编程知识 正文

Go study notes (38)—自定义包结构(子目录和父目录的关系、目录中的包名、未定义的包引用、没有主包执行错误、主包不能被引用)

时间:2023-05-03 16:15:03 阅读:273994 作者:3747

1. 包结构概述

在 Go 语言里,允许我们将同一个包的代码分隔成多个独立的源码文件来单独保存,只需要将这些文件放在同一个目录下即可。

我们创建的自定义的包需要将其放在 GOPATH 的 src 目录下(也可以是 src 目录下的某个子目录),而且两个不同的包不能放在同一目录下,这样会引起编译错误。

一个包中可以有任意多个文件,文件的名字也没有任何规定(但后缀必须是 .go ),这里我们假设包名就是 .go 的文件名(如果一个包有多个 .go 文件,则其中会有一个 .go 文件的文件名和包名相同)。

在 GOPATH 的 src 目录下创建如下结构文件:

wohu@wohu:~/gocode/src$ tree -L 3 .├── demo│ └── demo.go├── main│ └── main.go

demo.go 文件代码如下:

package demoimport "fmt"func PrintDemo() {fmt.Println("This is demo function ")}

main.go 文件代码如下:

package mainimport "demo"func main() {demo.PrintDemo()}

源码文件声明的包名可以与其所在目录的名称不同,只要这些文件声明的包名一致就可以。

对引用自定义包需要注意以下几点:

如果项目的目录不在 GOPATH 环境变量中,则需要把项目移到 GOPATH 所在的目录中,或者将项目所在的目录设置到 GOPATH 环境变量中,否则无法完成编译;

使用 import 语句导入包时,使用的是包所属文件夹的名称;

包中的函数名第一个字母要大写,否则无法在外部调用;

自定义包的包名不必与其所在文件夹的名称保持一致,但为了便于维护,建议保持一致;

调用自定义包时使用 包名.函数名 的方式,如上例: demo.PrintDemo() ;

2. 子目录包名与父目录关系

一个目录中,子目录包名需要与上级目录一样吗?答案是:可以不一样,也可以一样。

upload------uploadImage.go------uploadVideo.go------uploadText.go------download (目录)------send (目录)------receive (目录)

如在 GOPATH/src 中存在一个目录 upload ,那么 upload 目录下的 Go 源文件中定义的包名是 upload。在 upload 下边又存在 3 个目录,分别是 download 、 send 、 receive ,这 3 个子目录中可以声明不同的包名。

目录 download 中可以声明包名为 download,也可以声明为 upload;
目录 send 中可以声明包名为 send,也可以声明为 upload;
目录 receive 中可以声明包名为 receive,也可以声明为 upload;

也就是下级目录可以与上级目录声明不同的包名,也可以声明相同的包名。

3. 一个目录中,只能定义一个包名

在同一个目录下的源码文件都需要被声明为属于同一个代码包。如果该目录下有一个命令源码文件,那么为了让同在一个目录下的文件都通过编译,其他源码文件应该也声明属于 main 包。

下边创建一个示例工程,来详细地介绍 package 用法:

workspace---幸福的板凳---pkg---src------github.com---------wohu1104------------godemos---------------demo1------------------de1.go------------------de2.go------------------de3_test.go---------------demo3------------------de4.go------------------de5.go---------------demo2------------------main.go------------------demo2.go------------------demo3.go

如目录 demo3 中有两个文件,分别是 de4.go 和 de5.go ,这两个文件中定义包名必须相同。

有一个情况除外,如果 demo1 目录中有一个 go 文件为 de3_test.go , de3_test.go 中可以用 de1.go 或 de2.go 的包名加上 _test 作为包名,这个是 Golang test 的一个特性。

如在 demo1 目录中,定义 demo 包,内容分别是:

de1.go 文件

package demoimport ( "fmt")func Demo1() { fmt.Println("hello demo1")}

de2.go 文件:

package demoimport ( "fmt")func Demo2() { fmt.Println("hello demo2")}

de3_test.go 文件:

package demo_testimport ( "fmt")func TestDemo() { fmt.Println("test demo")} 4. 未定义的包名不能通过编译

如果包被主程序直接或间接的引用,而这个目录中存在没有定义包名的 Golang 文件(就算 Golang 文件为空也不行),在编译项目时,提示错误信息是:

...expected 'package', found 'EOF' 5. 执行没有 main 包的代码会报错

每一个可执行项目,必须有一个或多个 main 包;但在一个 main 包中,有且只能有一个 main 函数。如果项目中没有 main 包,那么在编译项目时,不会出现错误,如果在项目中使用 go run 来编译并启动项目,则会报错,错误信息是:

go run: cannot run non-main package

导致这个错误的原因是,项目中没有 main 包,不会生成可执行程序,所以无法启动项目。

虽然项目中可以同时存在多个 main 包,但是在编译时,只能指定一个 main 包进行编译。如果一个 main 包中,有多个 main 函数,则会无法编译,错误信息是:

main redeclared in this block 6. main 包不能被其它包引用

如果一个包被定义成 main 包,那么这个包还能否被其它包引用呢?答案是:不能。

举个例子,在 demo3 目录中的两个文件内容分别是:
de4.go 文件:

package mainimport ( "fmt")func Demo3Main4() { fmt.Println("demo3 main 4")}

de5.go文件

package mainimport "fmt"func Demo3Main5() { fmt.Println("demo3 main 5")}

在 demo2 目录的 main 函数中引用 demo3 目录中的 main 包,示例代码如下:

package mainimport ( demo3 "github.com/hzwy23/GoDemos/demo3" "fmt")func main() { fmt.Println("hello world") demo3.Demo3Main4()}

在编译时错误信息是:

import "demo3" is a program, not an importable package

错误信息显示 demo3 目录中是一个程序,而不是一个可导入的包。

7. 引用包

如在项目中,有一个打印日志信息的包,路径是 GOPATH/src/hzwy23/GoDemos/demo5/logs 。包名是 logs 。在 main 函数中想使用 logs 包来记录信息。只需要在发生引用的地方,导入 logs 这个包即可使用 logs 包中的可导出的变量、结构体、函数等。

在引用包时, import 指向的是包所在的目录相对于 GOPATH/src 的路径。当包被导入后,在使用包中的函数、变量、结构体时,不是使用目录作为前缀来访问包中变量、函数、机构体等,而是使用这个目录下声明的包名作为前缀来访问。

如 GOPATH 环境变量指向 /opt/workspace 目录,这个目录下边结构如下:

幸福的板凳pkgsrc---hzwy23------GoDemos---------demo5------------logs---------------write.go---------------config.go---------------logger.go------------app---------------main.go

在 main 函数中引用 logs 包的方法如下。

logger.go 文件

package logsimport ( "fmt")func Info(v ...interface{}) { fmt.Println(v...)}

main.go 文件:

package mainimport ( "github.com/hzwy23/GoDemos/demo5/logs")func main() { logs.Info("hello world")}

import 导入的是 logs 包在 GOPATH/src 中的路径。在使用包中的函数 Info 时,使用了 logs 作为前缀,这里的 logs 不是目录名,而是目录下 Golang 源文件中使用 package 关键字声明的包名。

如果 logs 目录下 Golang 源文件中声明的包名为 logger ,那么在使用 logger 包时,导入方法不变,但是调用 logger 包中 Info 函数时,需要使用 logger 作为前缀。示例代码如下。

logger.go 文件:

package loggerimport ( "fmt")func Info(v ...interface{}) { fmt.Println(v...)}

main.go 文件:

package mainimport ( "github.com/hzwy23/GoDemos/demo5/logs")func main() { logger.Info("hello world")} 8. 非 GOPATH 和 GOROOT 包不能被引用

如果自定义包不在 GOPATH/src 目录下边,那么还能被引用吗?答案是:不能,

Go 在编译时,只会查询 GOPATH/src 下边的包和 Go SDK 自身的包。如果需要引用的包不在 GOPATH/src 下边,又不是 Go SDK 自身的包,那么就不能被引用。

总结:
在定义包时,最好将包名设置成目录名,否则会人为的增加麻烦。如包名和目录名不一致,在导入时,需要记住包所在目录,导入后,需要知道目录中包的名字。如果包名和目录名一致,直接根据目录名,就知道包名,使用起来非常方便。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。