Go包管理

什么是包?为什么要使用包?

为了更好地组织类,Java 和GO都提供了包机制,用于区别类名的命名空间。

包的作用

  • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

  • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

  • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

比如Go内置的net包

net
├── http
├── internal
├── mail
├── rpc
├── smtp
├── testdata
├── textproto
└── url
以上是net包的一个目录结构,net本身是一个包,net目录下的http又是一个包。从这个大家可以看到,go语言的包其实就是我们计算机里的目录,或者叫文件夹,通过它们进行目录结构和文件组织,go只是对目录名字做了一个翻译,叫【包】而已。比如这里的net包其实就是net目录,http包其实就是http目录,这也是go语言中的一个命名习惯,包名和文件所在的目录名是一样的。

包的命名

go语言的包的命名,遵循简洁、小写、和go文件所在目录同名的原则,这样就便于我们引用,书写以及快速定位查找。在Java里面有一句话叫做见名知意。

比如go自带的http这个包,它这个http目录下的所有go文件都属于这个http包,所以我们使用http包里的函数、接口的时候,导入这个http包就可以了。

package main
import "net/http"
func main() {
	http.ListenAndServe("127.0.0.1:80",handler);
}

从这个例子可以看到,我们导入的是net/http,这在go里叫做全路径,因为http包在net里面,net是最顶级的包,所以必须使用全路径导入,go编译程序才能找到http这个包,和我们文件系统的目录路径是一样的。

因为有了全路径,所以命名的包名可以和其他库的一样,只要它们的全路径不同就可以了,使用全路径的导入,也增加了包名命名的灵活性。

对于自己或者公司开发的程序而言,我们一般采用域名作为顶级包名的方式,这样就不用担心和其他开发者包名重复的问题了。

main包

当把一个go文件的包名声明为main时,就等于告诉go编译程序,我这个是一个可执行的程序,那么go编译程序就会尝试把它编译为一个二进制的可执行文件。

一个main的包,一定会包含一个main()函数,这种我们也不陌生,比如C和Java都有main()函数,它是一个程序的入口,没这个函数,程序就无法执行。在go语言里,同时要满足main包和包含main()函数,才会被编译成一个可执行文件。

我们看一个Hello World的Go语言版本,来说明main 包。

package main


import (
	"fmt"
)


func main() {
	/* 这是我的第一个简单的程序 */
	fmt.Println("Hello, World!")
}

假设该go文件叫hello.go,放在 $GOPATH/src/hello 目录下,那么我们在这个目录下执行 go build 命令就会生成二进制的可执行文件,在window系统下生成的是 hello.exe ,在Unix,MAC和Linux下生成的是 hello ,我们在CMD或者终端里执行它,就可以看到控制台打印的:Hello,World。但是有一个很奇怪的现象,就是如果你创建的包名并不叫main,比如说叫test,我在test下面创建一个hello.go方法,执行起来肯定不对,但是如果我改成包名为main,就可以执行成。这在Java里面是不允许的,因为你在某个包下面创建的类必须属于这个包,go语言似乎可以动态的指定包,而不拘泥于当前类摆放的位置。

二进制可执行文件的名字,就是该main包的go文件所在目录的名字,因为hello.go在hello目录下,所以生成的可执行文件就是hello这个名字。

import 关键字

要想使用一个包,必须先导入它才可以使用,Go语言提供了 import 关键字来导入一个包,这个关键字告诉Go编译器到磁盘的哪里去找要想导入的包,所以导入的包必须是一个全路径的包,也就是包所在的位置。

import "fmt"
这就表示我们导入了 fmt 包,也就等于告诉go编译器,我们要使用这个包下面的代码。如果要导入多个包怎么办呢?Go语言还为我们提供的导入块。

import (
	"net/http"
	"fmt"
)

使用一对括号包含的导入块,每个包独占一行。

对于多于一个路径的包名,在代码中引用的时候,使用全路径最后一个包名作为引用的包名,比如net/http,我们在代码使用的是http,而不是net

现在我导入了包,那么编译的时候,go编译器去什么位置找他们呢?这里就要介绍下Go的环境变量了。Go有两个很重要的环境变量GOROOTGOPATH,这是两个定义路径的环境变量,GOROOT是安装Go的路径,比如/usr/local/goGOPATH是我们自己定义的开发者个人的工作空间,比如/home/flysnow/go


编译器会使用我们设置的这两个路径,再加上import导入的相对全路径来查找磁盘上的包,比如我们导入的fmt包,编译器最终找到的是/usr/local/go/fmt这个位置。

值得了解的是:对于包的查找,是有优先级的,编译器会优先在GOROOT里搜索,其次是GOPATH,一旦找到,就会马上停止搜索。如果最终都没找到,就报编译异常了。

远程包导入

互联网的时代,现在大家使用类似于Github共享代码的越来越多,如果有的Go包共享在Github上,我们一样有办法使用他们,这就是远程导入包了,或者是网络导入,Go天生就支持这种情况,所以我们可以很随意的使用Github上的Go库开发程序。
import "github.com/spf13/cobra"

这种导入,前提必须是该包托管在一个分布式的版本控制系统上,比如Github、Bitbucket等,并且是Public的权限,可以让我们直接访问它们。

编译在导入它们的时候,会先在GOPATH下搜索这个包,如果没有找到,就会使用go get工具从版本控制系统(GitHub)获取,并且会把获取到的源代码存储在GOPATH目录下对应URL的目录里,以供编译使用。

go get工具可以递归获取依赖包,如果github.com/spf13/cobra也引用了其他的远程包,该工具可以一并下载下来。

这种导入看起来是不是很像Java的maven里面的pom文件导入类。go里面直接简化了导入包的方法,需要哪个包直接导入就好,而不需要再去创建maven项目,在写一个pom类确实很方便。

命名导入

我们知道,在使用 import 关键字导入包之后,我们就可以在代码中通过包名使用该包下相应的函数、接口等。如果我们导入的包名正好有重复的怎么办呢?针对这种情况,Go语言可以让我们对导入的包重新命名,这就是命名导入。在Java中一个包的名称可能会有部分重复,但是没有全部重复的概念。

package main
import (
	"fmt"
	myfmt "mylib/fmt"
)
func main() {
	fmt.Println()
	myfmt.Println()
}

如果没有重新命名,那么对于编译器来说,这两个fmt它是区分不清楚的。重命名也很简单,在我们导入的时候,在包名的左侧,起一个新的包名就可以了。

Go语言规定,导入的包必须要使用,否则会包编译错误,这是一个非常好的规则,因为这样可以避免我们引用很多无用的代码而导致的代码臃肿和程序的庞大,因为很多时候,我们都不知道哪些包是否使用,这在C和Java上会经常遇到,有时候我们不得不借助工具来查找我们没有使用的文件、类型、方法和变量等,把它们清理掉。

但是有时候,我们需要导入一个包,但是又不使用它,按照规则,这是不行的,为此Go语言给我们提供了一个空白标志符_,只需要我们使用_重命名我们导入的包就可以了。

package main
import (
	_ "mylib/fmt"
)

包的init函数

每个包都可以有任意多个init函数,这些init函数都会在main函数之前执行。init函数通常用来做初始化变量、设置包或者其他需要在程序执行前的引导工作。比如上面我们讲的需要使用_空标志符来导入一个包的目的,就是想执行这个包里的init函数。

我们以数据库的驱动为例,Go语言为了统一关于数据库的访问,使用databases/sql抽象了一层数据库的操作,可以满足我们操作MYSQL、Postgre等数据库,这样不管我们使用这些数据库的哪个驱动,编码操作都是一样的,想换驱动的时候,就可以直接换掉,而不用修改具体的代码。

这些数据库驱动的实现,就是具体的,可以由任何人实现的,它的原理就是定义了init函数,在程序运行之前,把实现好的驱动注册到sql包里,这样我们就使用使用它操作数据库了。


package mysql
import (
	"database/sql"
)
func init() {
	sql.Register("mysql", &MySQLDriver{})
}

因为我们只是想执行这个mysql包的init方法,并不想使用这个包,所以我们在导入这个包的时候,需要使用 _ 重命名包名,避免编译错误。

import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user:password@/dbname")

看非常简洁,剩下针对的数据库的操作,都是使用的 database/sql 标准接口,如果我们想换一个mysql的驱动的话,只需要换个导入就可以了,灵活方便,这也是面向接口编程的便利。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值