包——基本概念,自定义包,创建包,导出包中的标志符

Posted acgame

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了包——基本概念,自定义包,创建包,导出包中的标志符相关的知识,希望对你有一定的参考价值。

1、基本概念

Go语言是使用包来组织源代码的,并实现命名空间的管理。任何源代码文件必须属于某个包。源码文件的第一行有效代码必须是 package pacakgeName 语句,通过该语句声明自己所在的包。

1)概念

Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然 Go 没有强制包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。

包可以定义在很深的目录中,包的定义是不包括目录路径的,但是包的引用一般是全路径引用。比如在 $GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为 package c,而不是声明为 package a/b/c,但是在“import”包 c 时,需要带上路径 import "a/b/c"。

包的习惯用法:

  • 包名一般是小写的,使用一个简短的命名。
  • 包名一般要和所在的目录同名。

2)包引用

标准包的源码位于 $GOROOT/src/ 下面,标准包可以直接引用。自定义的包和第三方包的源码必须放到 $GOPATH/src 目录下才能被引用。

包的引用路径有两种写法,一种是全路径,另一种是相对路径。

包的绝对路径就是“$GOROOT/src 或 $GOPATH/src”后面包的源码的全路径。

3)包引用格式

标准引用方式:import "fmt"

此时可以用“fmt.”作为前缀引用包内可导出元素,这是常用的一种方式。

别名引用方式:import F "fmt"

此时相当于给包 fmt 起了个别名 F,用“F.”代替标准的“fmt.”作为前缀引用 fmt 包内可导出元素。

省略方式:import . "fmt"

此时相当于把包 fmt 的命名空间直接合并到当前程序的命名空间中,使用 fmt 包内可导出元素可以不用前缀“fmt.”,直接引用。

仅执行包初始化init函数:import _ "fmt"

使用标准格式引用包,但是代码中却没有使用包,编译器会报错。如果包中有 init 初始化函数,则通过 import _ "packageName" 这种方式引用包,仅执行包的初始化函数,即使包没有 init 初始化函数,也不会引发编译器报错。

注意:

  • 一个包可以有多个 init 函数,包加载会执行全部的 init 函数,但并不能保证执行顺序,所以不建议在一个包中放入多个 init 函数,将需要初始化的逻辑放到一个 init 函数里面。
  • 包不能出现环形引用。比如包 a 引用了包 b,包 b 引用了包 c,如果包 c 又引用了包 a,则编译不能通过。
  • 包的重复引用是允许的。比如包 a 引用了包 b 和包 c,包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d,这种情况是允许的,并且 Go 编译器保证 d 的 init 函数只会执行一次。

init() 函数的特性如下:

  • 每个源码可以使用 1 个 init() 函数。
  • init() 函数会在程序执行前(main() 函数执行前)被自动调用。
  • 调用顺序为 main() 中引用的包,以深度优先顺序初始化。

例如,假设有这样的包引用关系:main→A→B→C,那么这些包的 init() 函数调用顺序为:

C.init→B.init→A.init→main

说明:

  • 同一个包中的多个 init() 函数的调用顺序不可预期。
  • init() 函数不能被其他函数调用。

 

4)包加载

Go 包的初始化有如下特点:

  • 包初始化程序从 main 函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图。
  • Go 编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化。
  • 单个包的初始化过程,先初始化常量,然后是全局变量,最后执行包的 init 函数(如果有)。

 

2、自定义包

我们创建的自定义的包最好就放在 GOPATH 的 src 目录下(或者 GOPATH src 的某个子目录),如果这个包只属于某个应用程序,可以直接放在应用程序的子目录下,但如果我们希望这个包可以被其他的应用程序共享,那就应该放在 GOPATH 的 src 目录下,每个包单独放在一个目录里,如果两个不同的包放在同一个目录下,会出现名字冲突的编译错误。

作为惯例,包的源代码应放在一个同名的文件夹下面。同一个包可以有任意多个文件,文件的名字也没有任何规定(但后续名必须是 .go),这里我们假设包名就是 .go 的文件名(如果一个包有多个 .go 文件,则其中会有一个 .go 文件的文件名和包名相同)。

如果我们希望在一个包里创建新的包,例如,在 my_package 包下面创建两个新的包 pkg1 和 pkg2,可以这么做:在 aGoPath/src/my_package 建两个子目录,例如 aGoPath/src/my_package/pkg1 和 aGoPath/src/my_package/pkg2,对应的包文件是 aGoPath/src/my_package/pkg1/pkg1.go 和 aGoPath/src/my_package/pkg2/pkg2.go。
之后,假如想导入 pkg2,使用 import my_package/pkg2 即可。Go语言标准库的源码树就是这样的结构。当然,my_package 目录可以有它自己的包,如 aGoPath/src/my_package/my_package.go 文件。

 

3、创建包

包要求在同一个目录下的所有文件的第一行添加如下代码,以标记该文件归属的包:

package 包名

包的特性如下:

  • 一个目录下的同级文件归属一个包。
  • 包名可以与其目录不同名。
  • 包名为 main 的包为应用程序的入口包,编译源码没有 main 包时,将无法编译输出可执行的文件。

每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。每个名字空间关联到一个特定的包,让我们给类型、函数等选择简短明了的名字,这样可以避免在我们使用它们的时候减少和其它部分名字的冲突。

每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包 API 的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。

 

4、导出包中的标识符

在 Go语言中,如果想在一个包里引用另外一个包里的标识符(如类型、变量、常量等)时,必须首先将被引用的标识符导出,将要导出的标识符的首字母大写就可以让引用者可以访问这些标识符了。

在被导出的结构体或接口中,如果它们的字段或方法首字母是大写,外部可以访问这些字段和方法。

 

以上是关于包——基本概念,自定义包,创建包,导出包中的标志符的主要内容,如果未能解决你的问题,请参考以下文章

golang中自定义包

怎么在java项目中调自定义的jar包中的类?

对Java语言中包修饰符封装的一些总结

OSGi:导出一个包而不导出同一个包中的几个类

linux服务器部署jar包以及shell脚本的书写

怎么样使用java包里面提供的类?自己定义的类名可以和包中的类名一样吗?怎么知道使用了java包里面的类?