包是 Go 語言中代碼組成和代碼編譯的主要方式。很多關于它們的基本信息已經在 4.2 章節中給出,最引人注目的便是可見性。現在我們來看看具體如何來使用自己寫的包。在下一節,我們將回顧一些標準庫中的包,自定義的包和標準庫以外的包。
當寫自己包的時候,要使用短小的不含有?`_`(下劃線)的小寫單詞來為文件命名。這里有個簡單例子來說明包是如何相互調用以及可見性是如何實現的。
當前目錄下(examples/chapter9)有一個名為 package_test.go 的程序, 它使用了自定義包 pack1 中 pack1.go 的代碼。這段程序(聯通編譯鏈接生成的 pack1.a)存放在當前目錄下一個名為 pack1 的文件夾下。所以鏈接器將包的對象和主程序對象鏈接在一起。
示例 9.4?[pack1.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_9/book/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`?這種托管在 Google Code、GitHub 和 Launchpad 等代碼網站上的包。
你可以通過如下命令安裝:
~~~
go install codesite.ext/author/goExample/goex
~~~
將一個名為?`codesite.ext/author/goExample/goex`?的 map 安裝在?`$GOROOT/src/`?目錄下。
通過以下方式,一次性安裝,并導入到你的代碼中:
~~~
import goex "codesite.ext/author/goExample/goex"
~~~
因此該包的 URL 將用作導入路徑。
在?`http://golang.org/cmd/goinstall/`?的?`go install`?文檔中列出了一些廣泛被使用的托管在網絡代碼倉庫的包的導入路徑
**包的初始化:**
程序的執行開始于導入包,初始化 main 包然后調用 main 函數。
一個沒有導入的包將通過分配初始值給所有的包級變量和調用源碼中定義的包級 init 函數來初始化。一個包可能有多個 init 函數甚至在一個源碼文件中。它們的執行是無序的。這是最好的例子來測定包的值是否只依賴于相同包下的其他值或者函數。
init 函數是不能被調用的。
導入的包在包自身初始化前被初始化,而一個包在程序執行中只能初始化一次。
**編譯并安裝一個包(參見第 9.7 節):**
在 Linux/OS X 下可以用類似第 4.3 節的 Makefile 腳本做到這一點:
~~~
include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
pack1.go\
pack1b.go\
include $(GOROOT)/src/Make.pkg
~~~
確保的它可執行性通過?`chmod 777 ./Makefile`。
上面腳本內的include語引入了相應的功能,將自動檢測機器的架構并調用正確的編譯器和鏈接器。
然后終端執行 make 或?`gomake`?工具:他們都會生成一個包含靜態庫 pack1.a 的 _obj 目錄。
go install(參見第 9.7 節,從 Go1 的首選方式)同樣復制 pack1.a 到本地的 $GOROOT/pkg 的目錄中一個以操作系統為名的子目錄下。像?`import "pack1"`?代替?`import "path to pack1"`,這樣只通過名字就可以將包在程序中導入。
當第 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.5**?使用第 6.6 節的斐波那契程序:
1)將斐波那契功能放入自己的 fibo 包中并通過主程序調用它,存儲最后輸入的值在函數的全局變量。
2)擴展 fibo 包將通過調用斐波那契的時候,操作也作為一個參數。實驗 "+" 和 “*”
main_fibo.go / fibonacci.go
- 前言
- 第一部分:學習 Go 語言
- 第1章:Go 語言的起源,發展與普及
- 1.1 起源與發展
- 1.2 語言的主要特性與發展的環境和影響因素
- 第2章:安裝與運行環境
- 2.1 平臺與架構
- 2.2 Go 環境變量
- 2.3 在 Linux 上安裝 Go
- 2.4 在 Mac OS X 上安裝 Go
- 2.5 在 Windows 上安裝 Go
- 2.6 安裝目錄清單
- 2.7 Go 運行時(runtime)
- 2.8 Go 解釋器
- 第3章:編輯器、集成開發環境與其它工具
- 3.1 Go 開發環境的基本要求
- 3.2 編輯器和集成開發環境
- 3.3 調試器
- 3.4 構建并運行 Go 程序
- 3.5 格式化代碼
- 3.6 生成代碼文檔
- 3.7 其它工具
- 3.8 Go 性能說明
- 3.9 與其它語言進行交互
- 第二部分:語言的核心結構與技術
- 第4章:基本結構和基本數據類型
- 4.1 文件名、關鍵字與標識符
- 4.2 Go 程序的基本結構和要素
- 4.3 常量
- 4.4 變量
- 4.5 基本類型和運算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 時間和日期
- 4.9 指針
- 第5章:控制結構
- 5.1 if-else 結構
- 5.2 測試多返回值函數的錯誤
- 5.3 switch 結構
- 5.4 for 結構
- 5.5 Break 與 continue
- 5.6 標簽與 goto
- 第6章:函數(function)
- 6.1 介紹
- 6.2 函數參數與返回值
- 6.3 傳遞變長參數
- 6.4 defer 和追蹤
- 6.5 內置函數
- 6.6 遞歸函數
- 6.7 將函數作為參數
- 6.8 閉包
- 6.9 應用閉包:將函數作為返回值
- 6.10 使用閉包調試
- 6.11 計算函數執行時間
- 6.12 通過內存緩存來提升性能
- 第7章:數組與切片
- 7.1 聲明和初始化
- 7.2 切片
- 7.3 For-range 結構
- 7.4 切片重組(reslice)
- 7.5 切片的復制與追加
- 7.6 字符串、數組和切片的應用
- 第8章:Map
- 8.1 聲明、初始化和 make
- 8.2 測試鍵值對是否存在及刪除元素
- 8.3 for-range 的配套用法
- 8.4 map 類型的切片
- 8.5 map 的排序
- 8.6 將 map 的鍵值對調
- 第9章:包(package)
- 9.1 標準庫概述
- 9.2 regexp 包
- 9.3 鎖和 sync 包
- 9.4 精密計算和 big 包
- 9.5 自定義包和可見性
- 9.6 為自定義包使用 godoc
- 9.7 使用 go install 安裝自定義包
- 9.8 自定義包的目錄結構、go install 和 go test
- 9.9 通過 Git 打包和安裝
- 9.10 Go 的外部包和項目
- 9.11 在 Go 程序中使用外部庫
- 第10章:結構(struct)與方法(method)
- 10.1 結構體定義
- 10.2 使用工廠方法創建結構體實例
- 10.3 使用自定義包中的結構體
- 10.4 帶標簽的結構體
- 10.5 匿名字段和內嵌結構體
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)與反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 類型斷言:如何檢測和轉換接口變量的類型
- 11.4 類型判斷:type-switch
- 11.5 測試一個值是否實現了某個接口
- 11.6 使用方法集與接口
- 11.7 第一個例子:使用 Sorter 接口排序
- 11.8 第二個例子:讀和寫
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高級編程
- 第12章 讀寫數據
- 12.1 讀取用戶的輸入
- 12.2 文件讀寫
- 12.3 文件拷貝
- 12.4 從命令行讀取參數
- 12.5 用buffer讀取文件
- 12.6 用切片讀寫文件
- 12.7 用 defer 關閉文件
- 12.8 使用接口的實際例子:fmt.Fprintf
- 12.9 Json 數據格式
- 12.10 XML 數據格式
- 12.11 用 Gob 傳輸數據
- 12.12 Go 中的密碼學
- 第13章 錯誤處理與測試
- 13.1 錯誤處理
- 13.2 運行時異常和 panic
- 13.3 從 panic 中恢復(Recover)
- 13.4 自定義包中的錯誤處理和 panicking
- 13.5 一種用閉包處理錯誤的模式
- 13.6 啟動外部命令和程序
- 13.7 Go 中的單元測試和基準測試
- 13.8 測試的具體例子
- 13.9 用(測試數據)表驅動測試
- 13.10 性能調試:分析并優化 Go 程序
- 第14章:協程(goroutine)與通道(channel)
- 14.1 并發、并行和協程
- 14.2 使用通道進行協程間通信
- 14.3 協程同步:關閉通道-對阻塞的通道進行測試
- 14.4 使用 select 切換協程
- 14.5 通道,超時和計時器(Ticker)
- 14.6 協程和恢復(recover)
- 第15章:網絡、模版與網頁應用
- 15.1 tcp服務器
- 15.2 一個簡單的web服務器
- 15.3 訪問并讀取頁面數據
- 15.4 寫一個簡單的網頁應用
- 第四部分:實際應用
- 第16章:常見的陷阱與錯誤
- 16.1 誤用短聲明導致變量覆蓋
- 16.2 誤用字符串
- 16.3 發生錯誤時使用defer關閉一個文件
- 16.5 不需要將一個指向切片的指針傳遞給函數
- 16.6 使用指針指向接口類型
- 16.7 使用值類型時誤用指針
- 16.8 誤用協程和通道
- 16.9 閉包和協程的使用
- 16.10 糟糕的錯誤處理
- 第17章:模式
- 17.1 關于逗號ok模式
- 第18章:出于性能考慮的實用代碼片段
- 18.1 字符串
- 18.2 數組和切片
- 18.3 映射
- 18.4 結構體
- 18.5 接口
- 18.6 函數
- 18.7 文件
- 18.8 協程(goroutine)與通道(channel)
- 18.9 網絡和網頁應用
- 18.10 其他
- 18.11 出于性能考慮的最佳實踐和建議
- 附錄