一、概述
Golang拥有超过100个标准包(可用go list std |wc -l查看)
任何包系统设计的目的都是简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立的模块以便于理解和更新,在每个模块更新的同时保持和程序中其他模块的相对独立,这种模块化的特性允许每个包能被其他的不同项目共享和重用,在项目范围内、全局范围内的复用;
每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。 每个名字空间关联到一个特定的包, 让我们给类型、 函数等选择简短明了的名字, 这样可以避免在我们使用它们的时候减少和其它部分名字的冲突。
每个包还通过控制包内名字的可见性和是否导出来实现封装特性。 通过限制包成员的可见性并隐藏包API的具体实现, 将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。 通过限制包内变量的可见性, 还可以强制用户通过某些特定函数来访问和更新内部变量, 这样可以保证
内部变量的一致性和并发时的互斥约束。
二、包的使用之导入路径
每个包是由一个全局唯一的字符串所标识的导入路径定位。 出现在import语句中的导入路径也是字符串。
import ( "fmt" "math/rand" "encoding/json" "golang.org/x/net/html" "github.com/go-sql-driver/mysql" )
三、包声明:
在每个Go源文件的开头都必须有包声明语句。 包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也称为包名)
例如, math/rand包的每个源文件的开头都包含 package rand 包声明语句, 所以当你导入这个包, 你就可以用rand.Int、 rand.Float64类似的方式访问包的成员。
关于默认包名一般采用导入路径名的最后一段的约定也有三种例外情况。
第一个例外, 包对应一个可执行程序, 也就是main包, 这时候main包本身的导入路径是无关紧要的。 名字为main的包是给go build( §10.7.3) 构建命令一个信息, 这个包编译完之后必须调用连接器生成一个可执行程序。
第二个例外, 包所在的目录中可能有一些文件名是以test.go为后缀的Go源文件( 译注:前面必须有其它的字符, 因为以``前缀的源文件是被忽略的) , 并且这些源文件声明的包名也是以_test为后缀名的。 这种目录可以包含两种包:一种普通包, 加一种则是测试的外部扩展包。
所有以_test为后缀包名的测试外部扩展包都由go test命令独立编译, 普通包和测试的外部扩展包是相互独立的。 测试的外部扩展包一般用来避免测试代码中的循环导入依赖。
第三个例外, 一些依赖版本号的管理工具会在导入路径后追加版本号信息, 例如"gopkg.in/yaml.v2"。 这种情况下包的名字并不包含版本号后缀, 而是yaml。
warning:如果我们想同时导入两个有着名字相同的包, 例如math/rand包和crypto/rand包, 那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。 这叫做导入包的重命名。
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
warning:匿名导入:
import _ "image/png" // register PNG decoder
1 // The jpeg command reads a PNG image from the standard input 2 // and writes it as a JPEG image to the standard output. 3 package main 4 import ( 5 "fmt" 6 "image" 7 "image/jpeg" 8 _ "image/png" // register PNG decoder 9 "io" 10 "os" 11 )
12 func main() { 13 if err := toJPEG(os.Stdin, os.Stdout); err != nil { 14 fmt.Fprintf(os.Stderr, "jpeg: %v ", err) 15 os.Exit(1) 16 } 17 } 18 func toJPEG(in io.Reader, out io.Writer) error { 19 img, kind, err := image.Decode(in) //这里可能需要匿名导入 20 if err != nil { 21 return err 22 } 23 fmt.Fprintln(os.Stderr, "Input format =", kind) 24 return jpeg.Encode(out, img, &jpeg.Options{Quality: 95}) 25 }
下面说说工作机制。 标准库还提供了GIF、 PNG和JPEG等格式图像的解码器,用户也可以提供自己的解码器, 但是为了保持程序体积较小, 很多解码器并没有被全部包含, 除非是明确需要支持的格式。 image.Decode函数在解码时会依次查询支持的格式列表。
每个格式驱动列表的每个入口指定了四件事情:格式的名称;一个用于描述这种图像数据开头部分模式的字符串, 用于解码器检测识别;一个Decode函数用于完成解码图像工作;一个DecodeConfig函数用于解码图像的大小和颜色空间的信息。 每个驱动入口是通过调用image.RegisterFormat函数注册, 一般是在每个格式包的init初始化函数中调用, 例如image/png包是这样注册的:
1 package png // image/png 2 func Decode(r io.Reader) (image.Image, error) 3 func DecodeConfig(r io.Reader) (image.Config, error) 4 func init() { 5 const pngHeader = "x89PNG x1a " 6 image.RegisterFormat("png", pngHeader, Decode, DecodeConfig) 7 }
最终的效果是, 主程序只需要匿名导入特定图像驱动包就可以用image.Decode解码对应格式的图像!
四、自定义自己的包:
包是函数和数据的集合。用 package 关键字定义一个包。文件名不需要与包名一致,但是你的文件夹必须要与包名字一致,这个很重要,所以为了简单起见,名字都是一样的。包名的约定是使用小写字符。Go 包可以由多个文件组成,但是使用相同的;
package 这一行。让我们在文件 test1.go 中定义一个叫做 pkg的包。
1 ├── pkg 2 │ └── myprint.go 3 4 //myprint.go 5 package pkg 6 import "fmt" 7 func MyPrintf(in string) { 8 fmt.Printf("%s", in) 9 }
在main包中测试:
1 package main 2 import ( 3 "fmt" 4 "./pkg" //带上路径 5 ) 6 7 func main() { 8 pkg.MyPrinf("my pkg test") 9 fmt.Printf("test ok") 10 } 11 12 //测试ok 13 稍作修改: 14 package main 15 import ( 16 "fmt" 17 "pkg" //不带上路径 18 ) 19 20 func main() { 21 pkg.MyPrinf("my pkg test") 22 fmt.Printf("test ok") 23 } 24 25 //closure.go:6:2: cannot find package "pkg" in any of: 26 /root/golang/go/src/pkg (from $GOROOT) 27 /root/src/pkg (from $GOPATH) 28 29 # 解决办法:将pkg包加个软连接到$GOROOT 或$GOPATH