一、概述
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\n", 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\r\n\x1a\n" 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