现在随便一个小程序可能就包含10000个函数,但是我们不可能一个个去构建,大部分还是来自于他人,这些函数通过类似包和模块的方式被重用
go语言的包超过100个,可以在终端中使用go list std |wc -l去查看,开源包可以通过http://godoc.org来检索
go带了一个工具包里面有各种简化工作区和包管理的小工具
包能够模块化的让我们理解和更新代码,同时包能够实现封装特性,不至于让代码变得乱,也便于我们在全球分发。Go语言的包编译起来很快,因为它有三个语言特性:1.导入包需在文件开头显式声明,这样编译器就无需读取和分析整个源文件来判断包的依赖关系,2.禁止包的环状依赖,包的关系是有向无环图,每个包可以被独立编译,而且可能是并发编译3.编译后包的目标文件不仅仅是记录包本身的导出信息,目标文件同时还记录了包的依赖关系
包一般是通过import自动导入,非标准库的包要写好互联网的路径地址,例如下面Go团队维护的HTML解析器和一个流行的第三方维护的MySQL驱动
import (
"fmt"
"math/rand"
"encoding/json"
"golang.org/x/net/html"
"github.com/go-sql-driver/mysql"
)
每个Go源文件开头都必须有包声明语句,它就是被其他包导入时的标识符
通过包名访问包成员
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println(rand.Int())
}
通常包的导入路径就是包名,但是可以重名,但他们的路径可能不同
关于把包名作为导入路径的最后一段也有三个例外,1.main包本身路径无关紧要,2.测试包,3.在导入路径后增加版本号信息,例如“gopkg.in/yaml.v2”
可以使用import一个个导入,也可以一起导入,还可以分割分组导入
import (
"fmt"
"html/template"
"os"
"golang.org/x/net/html"
"golang.org/x/net/ipv4"
)
如果两个包同名,导入的时候要重命名,这种命名只会在当前文件中起作用
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
重命名有三个好处,一个时区分重名包,二是化繁为简,把一些长包名复杂包名变短,三是可避免和包里面的重名变量混淆
循环导入编译器会提示错误
如果我们导入了包却没有使用会导致编译错误,但是呢有时候我么们想利用导入包产生的副作用:它会计算包级别的初始化表达式和执行导入包的init初始化函数,这是我们需要抑制unused import错误,我们可以使用 _ 来重命名导入的包。 _ 为空白标识符,不能被访问
import _ "image/png"
这个被称为包的匿名导入,它通常是用来实现一个编译时机制, 然后通过在main主程序入口选择性的导入附加的包。我们先来看看它的特性以及它是如何工作的
package main
import (
"fmt"
"image"
"image/jpeg"
"io"
"os"
)
func main() {
if err := toJPEG(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "jpeg:%v\n", err)
os.Exit(1)
}
}
func toJPEG(in io.Reader, out io.Reader) error {
img, kind, err := image.Decode(in)
if err != {
return err
}
fmt.Fprintln(os.Stderr,"Input format =",kind)
return jpeg.Encode(out,img,&jpeg.Options{Quality: 95})
}
如果我们给它一个合适的输入,它就能成功转换为输出
$ go build gopl.io/ch3/mandelbrot
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png
如果没有匿名导入,它可以编译,但是无法正确输出
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format
下面是代码的工作机制
package png // image/png
func Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)
func init() {
const pngHeader = "\x89PNG\r\n\x1a\n"
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
最终的效果是,主程序只需要匿名导入特定图像驱动包就可以用image.Decode解码对应格式的图像了
数据库包database/sql也是采用了类似的技术,让用户可以根据需要选择导入数据库驱动,如:
import (
"database/sql"
_ "github.com/lib/pq" // enable support for Postgres
_ "github.com/go-sql-driver/mysql" // enable support for MySQL
)
db, err = sql.Open("postgres", dbname) // OK
db, err = sql.Open("mysql", dbname) // OK
db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3"
当创建一个包,一般要用短小的命名;要尽量不使用局部变量的名称;一般是单数形式,但是有其他考虑也可以复数
当设计包名时需要考虑包名和成员是如何配合的,如下面的例子:
bytes.Equal flag.Int hettp.Get json.Marshal
我们再来看下strings命名模式,strings提供了有关于字符串的各项操作
package strings
func Index(needle, haystack string) int
type Replacer struct{ /* ... */ }
func NewReplacer(oldnew ...string) *Replacer
type Reader struct{ /* ... */ }
func NewReader(s string) *Reader
strings.Index \ strings.Replacer等都是操作
其它的包,可能只描述了单一数据类型,例如html/template和math/rand等,只暴露一个主要的数据结构与它相关的方法,还有一个以New命名的函数用于创建实例
package rand // "math/rand"
type Rand struct{ /* ... */ }
func New(source Source) *Rand
还可能导致一些名字的重复,例如template.Template或rand.Rand,这也就是为什么这些种类的包名往往特别短的原因之一
在另一个极端,还有像net/http包那样含有非常多的名字和种类不多的数据类型,因为他们要执行一个复杂的任务,尽管有近20种类型和更多的函数,但是包中最重要的成员名字确是简单明了的:Get,Post,Handle,Error,Client,Server