go中包的概念、導入與可見性

包是結構化代碼的一種方式:每一個程序都由包(一般簡稱爲 pkg)的概念組成,可使用自身的包或者從其它包中導入內容。linux

如同其它一些編程語言中的類庫或命名空間的概念,每一個 Go 文件都屬於且僅屬於一個包。一個包能夠由許多以 .go 爲擴展名的源文件組成,所以文件名和包名通常來講都是不相同的。git

你必須在源文件中非註釋的第一行指明這個文件屬於哪一個包,如:package mainpackage main表示一個可獨立執行的程序,每一個 Go 應用程序都包含一個名爲 main 的包。github

一個應用程序能夠包含不一樣的包,並且即便你只使用 main 包也沒必要把全部的代碼都寫在一個巨大的文件裏:你能夠用一些較小的文件,而且在每一個文件非註釋的第一行都使用 package main 來指明這些文件都屬於 main 包。若是你打算編譯包名不是爲 main 的源文件,如 pack1,編譯後產生的對象文件將會是 pack1.a 而不是可執行程序。另外要注意的是,全部的包名都應該使用小寫字母。編程

標準庫windows

在 Go 的安裝文件裏包含了一些能夠直接使用的包,即標準庫。在 Windows 下,標準庫的位置在 Go 根目錄下的子目錄 pkg\windows_386 中;在 Linux 下,標準庫在 Go 根目錄下的子目錄 pkg\linux_amd64 中(若是是安裝的是 32 位,則在 linux_386 目錄中)。通常狀況下,標準包會存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目錄下。編程語言

Go 的標準庫包含了大量的包(如:fmt 和 os),可是你也能夠建立本身的包。函數

若是想要構建一個程序,則包和包內的文件都必須以正確的順序進行編譯。包的依賴關係決定了其構建順序。編碼

屬於同一個包的源文件必須所有被一塊兒編譯,一個包便是編譯時的一個單元,所以根據慣例,每一個目錄都只包含一個包。spa

若是對一個包進行更改或從新編譯,全部引用了這個包的客戶端程序都必須所有從新編譯。code

Go 中的包模型採用了顯式依賴關係的機制來達到快速編譯的目的,編譯器會從後綴名爲 .o 的對象文件(須要且只須要這個文件)中提取傳遞依賴類型的信息。

若是 A.go 依賴 B.go,而 B.go 又依賴 C.go

  • 編譯 C.go, B.go, 而後是 A.go.
  • 爲了編譯 A.go, 編譯器讀取的是 B.o 而不是 C.o.

這種機制對於編譯大型的項目時能夠顯著地提高編譯速度。

每一段代碼只會被編譯一次

一個 Go 程序是經過 import 關鍵字將一組包連接在一塊兒。

import "fmt" 告訴 Go 編譯器這個程序須要使用 fmt 包(的函數,或其餘元素),fmt 包實現了格式化 IO(輸入/輸出)的函數。包名被封閉在半角雙引號 "" 中。若是你打算從已編譯的包中導入並加載公開聲明的方法,不須要插入已編譯包的源代碼。

若是須要多個包,它們能夠被分別導入:

import "fmt" import "os"

或:

import "fmt"; import "os"

可是還有更短且更優雅的方法(被稱爲因式分解關鍵字,該方法一樣適用於 const、var 和 type 的聲明或定義):

import (
   "fmt" "os" )

它甚至還能夠更短的形式,但使用 gofmt 後將會被強制換行:

import ("fmt"; "os")

當你導入多個包時,導入的順序會按照字母排序。

若是包名不是以 ./ 開頭,如 "fmt" 或者 "container/list",則 Go 會在全局文件進行查找;若是包名以 ./ 開頭,則 Go 會在相對目錄中查找;若是包名以 / 開頭(在 Windows 下也能夠這樣使用),則會在系統的絕對路徑中查找。

導入包即等同於包含了這個包的全部的代碼對象。

除了符號 _,包中全部代碼對象的標識符必須是惟一的,以免名稱衝突。可是相同的標識符能夠在不一樣的包中使用,由於可使用包名來區分它們。

包經過下面這個被編譯器強制執行的規則來決定是否將自身的代碼對象暴露給外部文件:

可見性規則

當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那麼使用這種形式的標識符的對象就能夠被外部包的 代碼所使用(客戶端程序須要先導入這個包),這被稱爲導出(像面嚮對象語言中的 public);標識符若是以小寫字母開頭,則對包外是不可見的,可是他們在整個包的內部是可見而且可用的(像面嚮對象語言中的 private )。

(大寫字母可使用任何 Unicode 編碼的字符,好比希臘文,不只僅是 ASCII 碼中的大寫字母)。

所以,在導入一個外部包後,可以且只可以訪問該包中導出的對象。

假設在包 pack1 中咱們有一個變量或函數叫作 Thing(以 T 開頭,因此它可以被導出),那麼在當前包中導入 pack1 包,Thing 就能夠像面嚮對象語言那樣使用點標記來調用:pack1.Thing(pack1 在這裏是不能夠省略的)。

所以包也能夠做爲命名空間使用,幫助避免命名衝突(名稱衝突):兩個包中的同名變量的區別在於他們的包名,例如 pack1.Thingpack2.Thing

你能夠經過使用包的別名來解決包名之間的名稱衝突,或者說根據你的我的喜愛對包名進行從新設置,如:import fm "fmt"。下面的代碼展現瞭如何使用包的別名:

示例 4.2 alias.go

package main

import fm "fmt" // alias3 func main() { fm.Println("hello, world") }

注意事項

若是你導入了一個包卻沒有使用它,則會在構建程序時引起錯誤,如 imported and not used: os,這正是遵循了 Go 的格言:「沒有沒必要要的代碼!「。

包的分級聲明和初始化

你能夠在使用 import 導入包以後定義或聲明 0 個或多個常量(const)、變量(var)和類型(type),這些對象的做用域都是全局的(在本包範圍內),因此能夠被本包中全部的函數調用(如 gotemplate.go 源文件中的 c 和 v),而後聲明一個或多個函數(func)。

 

Go 程序的通常結構

下面的程序能夠被順利編譯但什麼都作不了,不過這很好地展現了一個 Go 程序的首選結構。這種結構並無被強制要求,編譯器也不關心 main 函數在前仍是變量的聲明在前,但使用統一的結構可以在從上至下閱讀 Go 代碼時有更好的體驗。

全部的結構將在這一章或接下來的章節中進一步地解釋說明,但整體思路以下:

  • 在完成包的 import 以後,開始對常量、變量和類型的定義或聲明。
  • 若是存在 init 函數的話,則對該函數進行定義(這是一個特殊的函數,每一個含有該函數的包都會首先執行這個函數)。
  • 若是當前包是 main 包,則定義 main 函數。
  • 而後定義其他的函數,首先是類型的方法,接着是按照 main 函數中前後調用的順序來定義相關函數,若是有不少函數,則能夠按照字母順序來進行排序。

示例 4.4 gotemplate.go

package main

import ( "fmt" ) const c = "C" var v int = 5 type T struct{} func init() { // initialization of package } func main() { var a int Func1() // ... fmt.Println(a) } func (t T) Method1() { //... } func Func1() { // exported function Func1 //... }

Go 程序的執行(程序啓動)順序以下:

  1. 按順序導入全部被 main 包引用的其它包,而後在每一個包中執行以下流程:
  2. 若是該包又導入了其它的包,則從第一步開始遞歸執行,可是每一個包只會被導入一次。
  3. 而後以相反的順序在每一個包中初始化常量和變量,若是該包含有 init 函數的話,則調用該函數。
  4. 在完成這一切以後,main 也執行一樣的過程,最後調用 main 函數開始執行程序。
相關文章
相關標籤/搜索