《Go語言四十二章經》第七章 程式碼結構化
《Go語言四十二章經》第七章 程式碼結構化
作者:李驍
7.1 包的概念
包是結構化程式碼的一種方式:每個程式都由包(通常簡稱為 pkg)的概念組成,可以使用自身的包或者從其它包中匯入內容。
如同其它一些程式語言中的類庫或名稱空間的概念,每個 Go 檔案都屬於且僅屬於一個包。一個包可以由許多以 .go 為副檔名的原始檔組成,因此檔名和包名一般來說都是不相同的。
你必須在原始檔中非註釋的第一行指明這個檔案屬於哪個包,如:package main 。
package main表示一個可獨立執行的程式,每個 Go 應用程式都包含一個名為 main 的包。package main包下可以有多個檔案,但所有檔案中只能有一個main()方法,main()方法代表程式入口。
一個應用程式可以包含不同的包,而且即使你只使用 main 包也不必把所有的程式碼都寫在一個巨大的檔案裡:你可以用一些較小的檔案,並且在每個檔案非註釋的第一行都使用 package main 來指明這些檔案都屬於 main 包。如果你打算編譯包名不是為 main 的原始檔,如 pack1,編譯後產生的物件檔案將會是 pack1.a 而不是可執行程式。另外要注意的是,所有的包名都應該使用小寫字母。當然,main包是不能在其他文件import的,編譯器會報錯:
import "xx/xx" is a program, not an importable package。
簡單地說,在含有mian包的目錄下,你可以寫多個檔案,每個檔案非註釋的第一行都使用 package main 來指明這些檔案都屬於這個應用的 main 包,只有一個檔案能有mian() 方法,也就是應用程式的入口。main包不是必須的,只有在可執行的應用程式中需要。
7.2 包的匯入
一個 Go 程式是通過 import 關鍵字將一組包連結在一起。
import "fmt" 告訴 Go 編譯器這個程式需要使用 fmt 包(的函式,或其他元素),fmt 包實現了格式化 IO(輸入/輸出)的函式。包名被封閉在半形雙引號 "" 中。如果你打算從已編譯的包中匯入並載入公開宣告的方法,不需要插入已編譯包的原始碼。
import 其實是匯入目錄 ,而不是定義的package名字,雖然我們一般都會保持一致,但其實是可以隨便定義目錄名,只是使用時會很容易混亂,不建議這麼做。
例如:package big ,我們import"math/big" ,其實是在src中的src/math目錄。在程式碼中使用big.Int時,big指的才是Go檔案中定義的package名字。
當你匯入多個包時,最好按照字母順序排列包名,這樣做更加清晰易讀。
如果包名不是以 ./ ,如 "fmt" 或者 "container/list",則 Go 會在全域性檔案進行查詢;如果包名以 ./ 開頭,則 Go 會在相對目錄中查詢。
匯入包即等同於包含了這個包的所有的程式碼物件。
除了符號 _,包中所有程式碼物件的識別符號必須是唯一的,以避免名稱衝突。但是相同的識別符號可以在不同的包中使用,因為可以使用包名來區分它們。
匯入包的路徑的幾種情況:
- 第一種方式相對路徑
import"./module"//當前檔案同一目錄的module目錄, 此方式沒什麼用容易出錯,不建議用
- 第二種方式絕對路徑
import"LearnGo/init"//載入Gopath/src/LearnGo/init模組,一般建議這樣使用""
匯入多個包的常見的方式是:
import( "fmt" "net/http" )
呼叫匯入的包函式的一般方式:
fmt.Println("Hello World!")
下面展示一些特殊的import方式
-
點操作 import( . "fmt" )
這個點操作的含義就是這個包匯入之後在你呼叫這個包的函式時,你可以省略字首的包名,如可以省略的寫成Println("hello world!")
-
別名操作 別名操作顧名思義我們可以把包命名成另一個我們用起來容易記憶的名字。
import( f "fmt" )
別名操作呼叫包函式時字首變成了我們的字首,即f.Println("hello world")。在實際專案中有這樣使用,但請謹慎使用,不要廣泛採用這種形式。
- _操作 _操作其實是引入該包,而不直接使用包裡面的函式,而是呼叫了該包裡面的init函式。
import ( _ "github.com/revel/modules/testrunner/app" _ "guild_website/app" )
7.3 標準庫
在 Go 的安裝檔案裡包含了一些可以直接使用的包,即標準庫。在 Windows 下,標準庫的位置在 Go 根目錄下的子目錄 pkg\windows_386 中;在 Linux 下,標準庫在 Go 根目錄下的子目錄 pkg\linux_amd64 中(如果是安裝的是 32 位,則在 linux_386 目錄中)。Go 的標準庫包含了大量的包(如:fmt 和 os), 在$GoROOT/src中可以看到原始碼,也可以根據情況自行重新編譯。
完整列表可以在 Go Walker 檢視。
unsafe: 包含了一些打破 Go 語言“型別安全”的命令,一般的程式中不會被使用,可用在 C/C++ 程式的呼叫中。 syscall-os-os/exec: os: 提供給我們一個平臺無關性的作業系統功能介面,採用類Unix設計, 隱藏了不同作業系統間差異,讓不同的檔案系統和作業系統物件表現一致。 os/exec: 提供我們執行外部作業系統命令和程式的方式。 syscall: 底層的外部包,提供了作業系統底層呼叫的基本介面。 archive/tar 和 /zip-compress:壓縮(解壓縮)檔案功能。 fmt-io-bufio-path/filepath-flag: fmt: 提供了格式化輸入輸出功能。 io: 提供了基本輸入輸出功能,大多數是圍繞系統功能的封裝。 bufio: 緩衝輸入輸出功能的封裝。 path/filepath: 用來操作在當前系統中的目標檔名路徑。 flag: 對命令列引數的操作。 strings-strconv-unicode-regexp-bytes: strings: 提供對字串的操作。 strconv: 提供將字串轉換為基礎型別的功能。 unicode: 為 unicode 型的字串提供特殊的功能。 regexp: 正則表示式功能。 bytes: 提供對字元型分片的操作。 math-math/cmath-math/big-math/rand-sort: math: 基本的數學函式。 math/cmath: 對複數的操作。 math/rand: 偽隨機數生成。 sort: 為陣列排序和自定義集合。 math/big: 大數的實現和計算。 container-/list-ring-heap: 實現對集合的操作。 list: 雙鏈表。 ring: 環形連結串列。 time-log: time: 日期和時間的基本操作。 log: 記錄程式執行時產生的日誌。 encoding/Json-encoding/xml-text/template: encoding/Json: 讀取並解碼和寫入並編碼 Json 資料。 encoding/xml:簡單的 XML1.0 解析器。 text/template:生成像 HTML 一樣的資料與文字混合的資料驅動模板。 net-net/http-html: net: 網路資料的基本操作。 http: 提供了一個可擴充套件的 HTTP 伺服器和客戶端,解析 HTTP 請求和回覆。 html: HTML5 解析器。 runtime: Go 程式執行時的互動操作,例如垃圾回收和協程建立。 reflect: 實現通過程式執行時反射,讓程式操作任意型別的變數。
7.4 從 GitHub 安裝包
如果有人想安裝您的遠端專案到本地機器,開啟終端並執行(ffhelicopter是我在 GitHub 上的使用者名稱):
go get -u github.com/ffhelicopter/tmm
這樣現在這臺機器上的其他 Go 應用程式也可以通過匯入路徑:"github.com/ffhelicopter/tmm" 代替 "./ffhelicopter/tmm" 來使用。 也可以將其縮寫為:import ind "github.com/ffhelicopter/tmm";開發中一般這樣操作:
import "github.com/ffhelicopter/tmm"
Go 對包的版本管理做的不是很友好,不過現在有些第三方專案做的不錯,有興趣的同學可以瞭解下(glide、godep、govendor)。
7.5 匯入外部安裝包
如果你要在你的應用中使用一個或多個外部包,你可以使用 Go install在你的本地機器上安裝它們。Go install 是 Go 中自動包安裝工具:如需要將包安裝到本地它會從遠端倉庫下載包:檢出、編譯和安裝一氣呵成。
在包安裝前的先決條件是要自動處理包自身依賴關係的安裝。被依賴的包也會安裝到子目錄下,但是沒有文件和示例:可以到網上瀏覽。
Go install 使用了 GoPATH 變數
假設你想使用https://github.com/gocolly/colly 這種託管在 Google Code、GitHub 和 Launchpad 等程式碼網站上的包。
你可以通過如下命令安裝: Go install github.com/gocolly/colly 將一個名為 github.com/gocolly/colly安裝在$GoPATH/pkg/ 目錄下。
Go install/build都是用來編譯包和其依賴的包。
區別: Go build只對main包有效,在當前目錄編譯生成一個可執行的二進位制檔案(依賴包生成的靜態庫檔案放在$GoPATH/pkg)。
Go install一般生成靜態庫檔案放在$GoPATH/pkg目錄下,副檔名a。
如果為main包,執行Go buil則會在$GoPATH/bin 生成一個可執行的二進位制檔案。
7.6 包的分級宣告和初始化
你可以在使用 import 匯入包之後定義或宣告 0 個或多個常量(const)、變數(var)和型別(type),這些物件的作用域都是全域性的(在本包範圍內),所以可以被本包中所有的函式呼叫,然後宣告一個或多個函式(func)。
如果存在 init 函式的話,則對該函式進行定義(這是一個特殊的函式,每個含有該函式的包都會首先執行這個函式)。
程式開始執行並完成初始化後,第一個呼叫(程式的入口點)的函式是 main.main()(如果有 init() 函式則會先執行該函式)。
如果你的 main 包的原始碼沒有包含 main 函式,則會引發構建錯誤 undefined: main.main。main 函式既沒有引數,也沒有返回型別,這一點上 init 函式和 main 函式一樣。
main函式一旦返回就表示程式已成功執行並立即退出。
Go 程式的執行(程式啟動)順序如下: 程式的初始化和執行都起始於main包。如果main包還匯入了其它的包,那麼就會在編譯時將它們依次匯入。有時一個包會被多個包同時匯入,那麼它只會被匯入一次(例如很多包可能都會用到fmt包,但它只會被匯入一次,因為沒有必要匯入多次)。當一個包被匯入時,如果該包還匯入了其它的包,那麼會先將其它包匯入進來,然後再對這些包中的包級常量和變數進行初始化,接著執行init函式(如果有的話),依次類推。等所有被匯入的包都載入完畢了,就會開始對main包中的包級常量和變數進行初始化,然後執行main包中的init函式(如果存在的話),最後執行main函式。
Go語言中init函式用於包(package)的初始化,該函式是Go語言的一個重要特性,有下面的特徵:
- init函式是用於程式執行前做包的初始化的函式,比如初始化包裡的變數等
- 每個包可以擁有多個init函式
- 包的每個原始檔也可以擁有多個init函式
- 同一個包中多個init函式的執行順序Go語言沒有明確的定義(說明)
- 不同包的init函式按照包匯入的依賴關係決定該初始化函式的執行順序
- init函式不能被其他函式呼叫,而是在main函式執行之前,自動被呼叫
本書《Go語言四十二章經》內容在github上同步地址:https://github.com/ffhelicopter/Go42 本書《Go語言四十二章經》內容在簡書同步地址:https://www.jianshu.com/nb/29056963
雖然本書中例子都經過實際執行,但難免出現錯誤和不足之處,煩請您指出;如有建議也歡迎交流。 聯絡郵箱:[email protected]