#B 自定义和外部包的使用,编译,测试,文档和安装 #9.5 自定义包和可见性 包是Go语言中代码组成和代码编译的主要方式。很多关于它们的基本信息已经在4.2章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。 当写自己包的时候,要使用短小的不含有_(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。 当前目录下(examples/chapter9)有一个名为package_test.go的程序, 它使用了自定义包pack1中pack1.go的代码。这段程序(联通编译链接生成的pack1.a)存放在当前目录下一个名为pack1的文件夹下。所以链接器将包的对象和主程序对象链接在一起。 示例 9.4 [pack1.go](examples/chapter_9/pack1.go) package pack1 var Pack1Int int = 42 var PackFloat = 3.14 func ReturnStr() string { return "Hello main!" } 它包含了一个整型变量PackInt和一个返回字符串的函数ReturnStr。这段程序在运行时不做任何的事情,因为它不包含有一个main函数。 在主程序pack_test.go中这个包通过声明的方式被导入 import “./pack1/pack1” import的一般格式如下: import “包的路径或url地址“ 像 import "github.com/org1/pack1” 路径是指当前目录的相对路径。 示例 9.5 [package_test.go](examples/chapter_9/package_test.go) package main import ( "fmt" "./pack1/pack1" ) func main() { var test1 string test1 = pack1.ReturnStr() fmt.Printf("ReturnStr from package1: %s\n", test1) fmt.Printf(“Integer from package1: %d\n”, pack1.Pack1Int) // fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float) 输出结果: ReturnStr from package1: Hello main! Integer from package1: 42 如果包pack1和我们的程序在统同一路径下,我们可以通过"import ./pack1"这样的方式来引入,但这不被视为一个好的方法。 fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)这行代码试图访问一个未引用的变量或者函数,甚至没有编译。将会返回一个错误: cannot refer to unexported name pack1.pack1Float 主程序利用的包必须在主程序编写之前被编译。主程序中每个pack1项目都要通过包名来使用使用:pack1.Item。具体使用方法请参见示例4.6和4.7。 因此,按照惯例子目录和包之间有着密切的联系:为了区分不同包存放在不同的目录,每个包(所有属于这个包中的go文件)都存放在和包名相同的子目录下。 Import with . : import . “./pack1” 当使用.来做为包的别名时,你可以不通过包名来使用其中的项目。例如:test := ReturnStr()。 在当前的命名空间导入pack1包,一般是为了具有更好的测试效果。 Import with _ : import _ “./pack1/pack1” pack1包只导入其副作用,只执行了它的init函数并初始化了其中的全局变量 导入外部安装包: 如果你要在你的应用中使用一个或多个外部包,首先你必须使用go install(参见9.7章节)在你的本地机器上安装它们。 假设你想使用http://codesite.ext/author/goExample/goex这种托管在googlecode, github,launchpad等代码网站上的包。 你可以通过如下命令安装 go install codesite.ext/author/goExample/goex 将一个名为codesite.ext/author/goExample/goex的map安装在$GOROOT/src/目录下。 通过以下方式,一次性安装,并导入到你的代码中: import goex “codesite.ext/author/goExample/goex” 因此你项目的路径将成为导入包的网络地址 在http://golang.org/cmd/goinstall/的go install文档中列出了一些广泛被使用的托管在网络代码仓库的包的导入路径 包的初始化: 程序的执行开始于导入包,初始化main包然后调用main函数。 一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级init函数来初始化。一个包可能有多个init函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。 init函数是不能被调用的。 导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。 编译并安装一个包(参见9.7章节): 在Linux/OSX下可以用类似4.3章节的Makefile脚本做到这一点: include $(GOROOT)/src/Make.inc TARG=pack1 GOFILES=\ pack1.go\ pack1b.go\ include $(GOROOT)/src/Make.pkg 确保的它可执行性通过 chmod 777 ./Makefile。 内置声明了自动检测机器体系结构和使用正确的编译器和链接器的功能。 然后终端执行make或gomake:都会生成一个包含静态库pack1.a的_obj目录。 go install(参见9.7章节,从Go1的首选方式)同样复制pack1.a到本地的$GOROOT/pkg的目录中一个以操作系统为名的子目录下。像 import "pack1"代替imort "path to pack1",这样只通过名字就可以将包在程序中导入。 如果不可取或不被允许,通过6/8g使用-I选项来编译: 6g—I map_pack1 package_test.go # where map_pack1 is the map which contains pack1.a (I选项让编译器查找选项后的目录下是否包含这个包) 使用6/8l的-L选项链接: 6l—L map_pack1 package_test.6 当第13章我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。 练习: 问题9.1: (a)一个包能分成多个源文件么? (b)一个源文件是否能包含多个包? 练习 9.3: 创建一个程序main_greetings.go能够和用户说"Good Day"或者"Good Night"。不同的问候应该放到greetings包中。 在同一个包中创建一个ISAM函数返回一个布尔值用来判断当前时间是AM还是PM,同样创建IsAfternoon和IsEvening函数。 使用main_greetings作出合适的问候。 (提示:使用time包) 练习 9.4:创建一个程序main_oddven.go判断前100个整数是不是偶数,包内同时包含测试的功能。 练习 9.6:使用6.6章节的斐波那契程序 (1)将斐波那契功能放入自己的fibo包中并通过主程序调用它,存储最后输入的值在函数的全局变量。 (2)扩展fibo包将通过调用斐波那契的时候,操作也作为一个参数。实验"+"和“*” main_fibo.go / fibonacci.go