Golang 包瞭解以及程式的執行
引言
Go 語言是使用包來組織原始碼的,包(package)是多個 Go 原始碼的集合,是一種高階的程式碼複用方案。Go 語言中為我們提供了很多內建包,如 fmt、os、io等。
任何原始碼檔案必須屬於某個包,同時原始碼檔案的第一行有效程式碼必須是package pacakgeName 語句,通過該語句宣告自己所在的包。
一、包介紹
二、標準庫
一、包介紹
1. 包的基本概念
Go 語言的包藉助了目錄樹的組織形式,一般包的名稱就是其原始檔所在目錄的名稱,雖然Go語言沒有強制要求包名必須和其所在的目錄名同名,但還是建議包名和所在目錄同名,這樣結構更清晰。
包可以定義在很深的目錄中,包名的定義是不包括目錄路徑的,但是包在引用時一般使用全路徑引用。
比如在GOPATH/src/a/b/ 下定義一個包c。在包c的原始碼中只需宣告為package c,而不是宣告為package a/b/c,但是在匯入c包時,需要帶上路徑,例如import "a/b/c"。
2. 包的用法
包名一般是小寫的,使用一個簡短且有意義的名稱
包名一般要和所在的目錄同名,也可以不同,包名中不能包含-等特殊符號
包一般使用域名作為目錄名稱,這樣能保證包名的唯一性,比如 GitHub 專案的包一般會放到GOPATH/src/github.com/userName/projectName目錄下
包名為 main 的包為應用程式的入口包,編譯不包含 main 包的原始碼檔案時不會得到可執行檔案
一個資料夾下的所有原始碼檔案只能屬於同一個包,同樣屬於同一個包的原始碼檔案不能放在多個資料夾下。
3. 識別符號可見性
在同一個包內部宣告的識別符號都位於同一個名稱空間下,在不同的包內部宣告的識別符號就屬於不同的名稱空間。想要在包的外部使用包內部的識別符號就需要新增包名字首,例如fmt.Println("Hello world!"),就是指呼叫 fmt 包中的Println 函式。
如果想讓一個包中的識別符號(如變數、常量、型別、函式等)能被外部的包使用,那麼識別符號必須是對外可見的(public)。在Go語言中是通過識別符號的首字母大/小寫來控制識別符號的對外可見(public)/不可見(private)的。在一個包內部只有首字母大寫的識別符號才是對外可見的。
例如我們定義一個名為demo的包,在其中定義了若干識別符號。在另外一個包中並不是所有的識別符號都能通過demo.字首訪問到,因為只有那些首字母是大寫的識別符號才是對外可見的。
package demo import "fmt" // 包級別識別符號的可見性 // num 定義一個全域性整型變數 // 首字母小寫,對外不可見(只能在當前包內使用) var num = 100 // Mode 定義一個常量 // 首字母大寫,對外可見(可在其它包中使用) const Mode = 1 // person 定義一個代表人的結構體 // 首字母小寫,對外不可見(只能在當前包內使用) type person struct { name string Age int } // Add 返回兩個整數和的函式 // 首字母大寫,對外可見(可在其它包中使用) func Add(x, y int) int { return x + y } // sayHi 打招呼的函式 // 首字母小寫,對外不可見(只能在當前包內使用) func sayHi() { var myName = "七米" // 函式區域性變數,只能在當前函式內使用 fmt.Println(myName) }
同樣的規則也適用於結構體,結構體中可匯出欄位的欄位名稱必須首字母大寫。
type Student struct { Name string // 可在包外訪問的方法 class string // 僅限包內訪問的欄位 }
4. 包的引入
- 要在當前包中使用另外一個包的內容就需要使用
import關鍵字
引入這個包,並且 import 語句通常放在檔案的開頭,package 宣告語句的下方。 - 完整的引入宣告語句格式如下:
import importname "path/to/package"
其中:
importname:
引入的包名,通常都省略。預設值為引入包的包名。
path/to/package:
引入包的路徑名稱,必須使用雙引號包裹起來。
Go語言中禁止迴圈匯入包
- 一個Go原始碼檔案中可以同時引入多個包,例如:
import "fmt" import "net/http" import "os"
- 當然可以使用批量引入的方式
import ( "fmt" "net/http" "os" )
當引入的多個包中存在相同的包名或者想自行為某個引入的包設定一個新包名時,都需要通過importname指定一個在當前檔案中使用的新包名。例如,在引入fmt包時為其指定一個新包名f。
import f "fmt"
這樣在當前這個檔案中就可以通過使用f來呼叫fmt包中的函式了。
f.Println("Hello world!")
如果引入一個包的時候為其設定了一個特殊_作為包名,那麼這個包的引入方式就稱為匿名引入。一個包被匿名引入的目的主要是為了載入這個包,從而使得這個包中的資源得以初始化。 被匿名引入的包中的init函式將被執行並且僅執行一遍。
import _ "github.com/go-sql-driver/mysql"
匿名引入的包與其他方式匯入的包一樣都會被編譯到可執行檔案中。
需要注意的是,Go語言中不允許引入包卻不在程式碼中使用這個包的內容,如果引入了未使用的包則會觸發編譯錯誤。
二、標準庫
1. 標準庫概述
標準庫API
在 Go 的安裝檔案裡包含了一些可以直接使用的包,即標準庫。
在Windows下,標準庫的位置在 Go 根目錄下的子目錄 pkg\windows_amd64中 ; 在 Linux下,標準庫在 Go 根目錄下的子目錄 pkg\linux_amd64中(如果是安裝的是32位,則在(linux_386目錄中)。
一般情況下,標準包會存放在$GOROOT/pkg/$GOOS_$GOARCH/目錄下。
Go 的標準庫包含了大量的包(如: fmt和os),但是也可以建立自己的包。
如果想要構建一個程式,則包和包內的檔案都必須以正確的順序進行編譯。包的依賴關係決定了其構建順序。屬於同一個包的原始檔必須全部被一起編譯,一個包即是編譯時的一個單元,因此根據慣例,每個目錄都只包含一個包。
如果對一個包進行更改或重新編譯,所有引用了這個包的客戶端程式都必須全部重新編譯。
Go 中的包模型採用了顯式依賴關係的機制來達到快速編譯的目的,編譯器會從字尾名為.o的物件檔案(需要且只需要這個檔案)中提取傳遞依賴型別的資訊。
如果A.go依賴B.go,而B.go又依賴c.go:
編譯c.go,B.go,然後是A.go
為了編譯A.go,編譯器讀取的是 B.o 而不是c.o
這種機制對於編譯大型的專案時可以顯著地提升編譯速度。
示例:
一個程式包含兩個包: cat和main,其中 add 包中包含兩個變數 Name 和 Age,請問 main 包中如何訪問 Name 和 Age。
package cat import "fmt" var Name string = "tom" var Age int = 5 //初始化函式 func init() { fmt.Println("this is cat package") fmt.Println("init函式修改前:", Name, Age) Name = "jack" Age = 3 fmt.Println("init函式修改後:", Name, Age) }
package main import ( //包的別名定義 a "dev_code/day9/example3/cat" b "fmt" ) func main() { b.Println("貓的名字:", a.Name) b.Println("貓的年齡:", a.Age) }
輸出結果如下
this is cat package init函式修改前: tom 5 init函式修改後: jack 3 貓的名字: jack 貓的年齡: 3
- 總結:
呼叫其他包程式載入順序:cat.go中的全域性變數----->cat.go中的init()函式--------->main.go中的main()函式
2. 標準庫常見的包及其功能
- Go語言的標準庫以包的方式提供支援,下表列出了Go語言標準庫中常見的包及其功能。
Go語言標準庫包名 | 功 能 |
---|---|
bufio | 帶緩衝的 I/O 操作 |
bytes | 實現位元組操作 |
container | 封裝堆、列表和環形列表等容器 |
crypto | 加密演算法 |
database | 資料庫驅動和介面 |
debug | 各種除錯檔案格式訪問及除錯功能 |
encoding | 常見演算法如 JSON、XML、Base64 等 |
flag | 命令列解析 |
fmt | 格式化操作 |
go | Go語言的詞法、語法樹、型別等。可通過這個包進行程式碼資訊提取和修改 |
html | HTML 轉義及模板系統 |
image | 常見圖形格式的訪問及生成 |
io | 實現 I/O 原始訪問介面及訪問封裝 |
math | 數學庫 |
net |
網路庫,支援 Socket、HTTP、郵件、RPC、SMTP 等 |
os | 作業系統平臺不依賴平臺操作封裝 |
path | 相容各作業系統的路徑操作實用函式 |
plugin | Go 1.7 加入的外掛系統。支援將程式碼編譯為外掛,按需載入 |
reflect | 語言反射支援。可以動態獲得程式碼中的型別資訊,獲取和修改變數的值 |
regexp |
正規表示式封裝 |
runtime | 執行時介面 |
sort | 排序介面 |
strings | 字串轉換、解析及實用函式 |
time | 時間介面 |
text | 文字模板及 Token 詞法器 |
3. 程式執行順序
Go 程式的執行(程式啟動)順序如下:
① 按順序匯入所有被 main 包引用的其它包,然後在每個包中執行如下流程:
② 如果該包又匯入了其它的包,則從第一步開始遞迴執行,但是每個包只會被匯入一次。
③ 然後以相反的順序在每個包中初始化常量和變數,如果該包含有init 函式的話,則呼叫該函式。
④ 在完成這一切之後,main 也執行同樣的過程,最後呼叫 main 函式開始執行程式。
demo.go
package demo import "fmt" var Name string = "this is demo package" var Age int = 20 func init() { fmt.Println("this is demo init()") fmt.Println("demo.package.Name=", Name) fmt.Println("demo.package.Age=", Age) Name = "this is demo New" Age = 200 fmt.Println("demo.package.Name=", Name) fmt.Println("demo.package.Age=", Age) }
main.go
package main import ( "dev_code/day9/example4/test" "fmt" ) func main() { //main---->test----->demo fmt.Println("main.package:", test.Name) fmt.Println("main.package:", test.Age) }
test.go
package test import ( "fmt" //對指定包做初始化,並不做呼叫處理 _ "dev_code/day9/example4/demo" ) var Name string = "this is test package" var Age int = 10 func init() { fmt.Println("this is test init()") fmt.Println("test.package.Name=", Name) fmt.Println("test.package.Age=", Age) Name = "this is test New" Age = 100 fmt.Println("test.package.Name=", Name) fmt.Println("test.package.Age=", Age) }
執行結果如下
this is demo init() demo.package.Name= this is demo package demo.package.Age= 20 demo.package.Name= this is demo New demo.package.Age= 200 this is test init() test.package.Name= this is test package test.package.Age= 10 test.package.Name= this is test New test.package.Age= 100 main.package: this is test New main.package: 100
包引用的時候順序:
main引用add包,add再引用demo包;
編譯執行流程:
先從demo包這裡編譯載入裡面的全域性變數,然後執行init函式,當init函式執行完成以後再去執行add中的全域性變數,再執行裡面的init函式,最後執行main包中的函式