在 Go 语言里,允许我们将同一个包的代码分隔成多个独立的源码文件来单独保存,只需要将这些文件放在同一个目录下即可。
我们创建的自定义的包需要将其放在 GOPATH 的 src 目录下(也可以是 src 目录下的某个子目录),而且两个不同的包不能放在同一目录下,这样会引起编译错误。
一个包中可以有任意多个文件,文件的名字也没有任何规定(但后缀必须是 .go ),这里我们假设包名就是 .go 的文件名(如果一个包有多个 .go 文件,则其中会有一个 .go 文件的文件名和包名相同)。
在 GOPATH 的 src 目录下创建如下结构文件:
wohu@wohu:~/gocode/src$ tree -L 3 .├── demo│ └── demo.go├── main│ └── main.godemo.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 文件:
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 自身的包,那么就不能被引用。
总结:
在定义包时,最好将包名设置成目录名,否则会人为的增加麻烦。如包名和目录名不一致,在导入时,需要记住包所在目录,导入后,需要知道目录中包的名字。如果包名和目录名一致,直接根据目录名,就知道包名,使用起来非常方便。