您當前的位置 :云南之窗 > 美食 >  內容正文
投稿

一文讀懂Go中軟件包概念

云南之窗 2020-03-23 09:55:41 來源: 閱讀:

Go編程語言的軟件包管理和部署的完整概述

如果您熟悉Java或NodeJS之類的語言,那么您可能非常熟悉軟件包。 包不過是帶有一些代碼文件的目錄,該目錄從單個引用點公開了不同的變量(功能)。 讓我解釋一下,這意味著什么。

想象一下,在任何項目中進行工作時,您都需要不斷地使用上千種功能。 其中一些功能具有共同的行為。 例如,toUpperCase和toLowerCase函數可轉換字符串的大小寫,因此您可以將它們寫入單個文件(可能為case.go)。 還有其他函數對字符串數據類型執行其他一些操作,因此您也將它們寫在單獨的文件中。

由于您有許多文件可以處理字符串數據類型,因此您創建了一個名為string的目錄,并將所有與字符串相關的文件放入其中。 最后,將所有這些目錄放在一個父目錄中,該目錄將成為您的軟件包。 整個程序包結構如下所示。

package-name├── string|  ├── case.go|  ├── trim.go|  └── misc.go└── number   ├── arithmetics.go   └── primes.go

我將詳細解釋如何從包中導入函數和變量,以及如何將所有內容融合在一起形成一個包,但是現在,將您的包想象成一個包含.go文件的目錄。

每個Go程序都必須是某些程序包的一部分。 如Go入門課程中所述,獨立的可執行Go程序必須具有包main聲明。 如果程序是主軟件包的一部分,則go install將創建一個二進制文件; 在執行時調用程序的主要功能。 如果程序不是main包的一部分,則使用go install命令創建包庫文件。 不用擔心,我將在以后的主題中解釋所有這些內容。

讓我們創建一個可執行程序包。 眾所周知,要創建一個二進制可執行文件,我們需要程序成為主程序包的一部分,并且它必須具有main函數,該函數是執行的入口點。

軟件包名稱是src目錄中包含的目錄的名稱。 在上述情況下,因為app是src目錄的子目錄,所以app是軟件包。 因此,執行go install app命令在GOPATH的src目錄中查找app子目錄。 然后,它編譯了程序包,并在bin目錄中創建了應用二進制可執行文件,該文件應可從終端執行,因為PATH中的bin目錄。

像上面示例中的package main應該是代碼的第一行的包聲明,它可以與包名稱不同。 因此,您可能會發現某些軟件包,其中軟件包名稱(目錄名稱)與軟件包聲明不同。 導入程序包時,程序包聲明用于創建程序包引用變量,如本文稍后所述。

go install <package> 命令在給定的軟件包目錄中查找具有主軟件包聲明的任何文件。 如果找到文件,則Go知道這是一個可執行程序,因此需要創建一個二進制文件。 一個包可以有許多文件,但只有一個具有主要功能的文件,因為該文件將是執行的入口。

如果軟件包不包含帶有main軟件包聲明的文件,則Go會在pkg目錄中創建一個軟件包庫0(.a)文件。

由于app不是可執行程序包,因此它在pkg目錄中創建了app.a文件。 該文件不是二進制文件,因此無法執行。

包命名約定

Go社區建議對軟件包使用簡單的名稱。 例如,用于字符串實用程序功能的strutils,或用于HTTP請求相關功能的http。 應該避免使用下劃線,連字符或大小寫混合的軟件包名稱。




創建一個包

正如我們所討論的,有兩種類型的軟件包。 可執行程序包和實用程序包。 可執行包是您的主要應用程序,因為您將在運行它。 實用程序包不是自可執行的,相反,它通過提供實用程序功能和其他重要資產來增強可執行程序包的功能。

眾所周知,一個包只不過是一個目錄,讓我們在src中創建greet目錄,并在其中創建幾個文件。 這次,我們將在每個文件的頂部編寫一個包聲明 package greet,以聲明這是一個實用程序包。

導出成員

實用程序包應該為導入它的包提供一些變量。 與JavaScript中的 export 語法一樣,如果變量名稱以大寫字母開頭,則Go會導出變量。 所有其他不以大寫字母開頭的變量對程序包都是私有的。

從現在開始,我將使用 variable 來描述導出成員,但是導出成員可以是常量,映射,函數,結構,數組,切片等任何類型。

讓我們從day.go文件中導出一個問候變量。

在上面的程序中,Morning變量將從包中導出,但是morning變量不會,它以小寫字母開頭,因此不會。

導入包

現在,我們需要一個可執行包,它將使用我們的greet包。 讓我們在src內創建一個app目錄,并創建帶有main包聲明和main函數的entry.go文件。 請注意,Go包沒有像在Node中index.js這樣的入口文件命名系統。 對于可執行程序包,具有main功能的文件是要執行的入口文件。

要導入軟件包,我們使用import語法,后跟軟件包名稱。

與其他編程語言不同,包名稱也可以是諸如some-dir / greet的子路徑,而Go會自動為我們解析到greet包的路徑,如前面的嵌套包主題所示。

首先轉到GOROOT / src目錄中的包目錄,如果找不到包,則查找GOPATH / src。 由于fmt軟件包是Go的標準庫(位于GOROOT / src中)的一部分,因此從那里導入。 由于Go無法在GOROOT中找到greet包,因此它將在GOPATH / src中進行查找,并且我們已經在其中了。

上面的程序拋出編譯錯誤,因為從包中看不到morning變量。 如您所見,我們使用. (點)表示法,用于從包中訪問導出的成員。 導入程序包時,Go使用程序包的程序包聲明創建一個全局變量。 在上述情況下,greet是Go創建的全局變量,因為我們在greet軟件包中包含的程序中使用了package greet聲明。

我們可以使用分組語法(括號)將fmt和greet包一起導入。 這次,我們的程序將編譯得很好,因為可以從包外部獲得Morning變量。

嵌套包

我們可以將包嵌套在包中。 由于對于Go,包只是目錄,就像在現有包中創建子目錄一樣。 我們要做的就是提供嵌套包的相對路徑。

包編譯

如上一課所述,go run命令編譯并執行程序。 我們知道,go install命令可以編譯軟件包并創建二進制可執行文件或軟件包庫文件。 這是為了避免每次編譯(導入這些程序包的程序)每次都編譯程序包。 go install會預編譯一個軟件包,而Go則引用.a文件。

通常,當您安裝第三方軟件包時,Go會編譯該軟件包并創建軟件包存檔文件。 如果您在本地編寫軟件包,則IDE可能會在將文件保存在軟件包中或在軟件包被修改后立即創建軟件包庫文件。 如果已安裝Go插件,則VSCode在保存時會編譯該軟件包。


包初始化

當我們運行Go程序時,Go編譯器會按照一定的執行順序執行程序包,程序包中的文件以及程序包中的變量聲明。

包范圍

范圍是代碼塊中可訪問定義變量的區域。 包作用域是包中的一個區域,在該區域中可從包內(包中的所有文件)訪問聲明的變量。 此區域是軟件包中任何文件的最上面的塊。

看一下go run命令。 這次,我們不再使用一個文件,而是使用一種全局模式將所有文件都包含在應用程序包中以便執行。 Go具有足夠的智能,可以找出應用程序的0=6圖片;v'entry.go入口點,因為它具有main函數。 我們還可以使用如下所示的命令(文件名順序無關緊要)。

go run src/app/version.go src/app/entry.go

go install或go build命令需要一個軟件包名稱,其中包括軟件包中的所有文件,因此我們不必像上面那樣指定它們。

回到我們的主要問題,我們可以在包的任何地方使用在version.go文件中聲明的變量version,即使它沒有導出(如Version),因為它是在包范圍內聲明的。 如果在函數中聲明了version變量,則說明該version 變量不在包范圍內,并且上述程序無法編譯。

不允許在同一包中重新聲明具有相同名稱的全局變量。 因此,一旦聲明了版本變量,就不能在程序包范圍內對其進行重新聲明。 但是您可以隨意在其他地方重新聲明。

變量初始化

當變量a依賴于另一個變量b時,應事先定義b,否則程序將無法編譯。 Go在函數內部遵循此規則。

但是,當這些變量在包范圍內定義時,它們將在初始化周期中聲明。 讓我們看看下面的簡單示例。

在上面的示例中,第一個c被聲明,因為它的值已經被聲明。 在以后的初始化周期中,聲明了b,因為它取決于c并且已經聲明了c的值。 在最后的初始化周期中,聲明a并將其分配給b的值。 Go可以處理如下所示的復雜初始化周期。

在上面的示例中,將首先聲明c,然后聲明b,因為其值取決于c,最后是a,因為其值取決于b。 您應該避免像下面這樣的初始化循環。

包范圍的另一個示例是,在單獨文件中具有函數f,該函數從主文件引用變量c。

初始化函數

像main函數一樣,初始化包時,Go會調用init函數。 它不帶任何參數,也不返回任何值。 Go隱式聲明了init函數,因此您無法從任何地方引用它(或像init()一樣調用它)。 您可以在文件或包中聲明多個init函數。 文件中init函數的執行順序將根據它們出現的順序。

您可以在包中的任何位置聲明init函數。 這些初始化函數以文件名字母順序調用。

畢竟,要執行init函數,然后才能調用main函數。 因此,init函數的主要工作是初始化無法在全局上下文中初始化的全局變量。 例如,數組的初始化。

由于for語法在包范圍內無效,因此我們可以在init函數內部使用for循環來初始化大小為10的數組整數。

包別名

導入包時,請使用包的包聲明創建一個變量。 如果要導入多個具有相同名稱的軟件包,則將導致沖突。

// parent.gopackage greetvar Message = "Hey there. I am parent."// child.gopackage greetvar Message = "Hey there. I am child."


因此,我們使用包別名。 我們在import關鍵字和程序包名稱之間聲明一個變量名,該變量名成為引用該程序包的新變量。

在上面的示例中,greet / greet包現在由子變量引用。 如果您注意到的話,我們在greet包中使用下劃線作為別名。 下劃線是Go中的一個特殊字符,用作空容器。 由于我們導入的是greet包,但未使用它,因此Go編譯器會抱怨它。 為了避免這種情況,我們將對該包的引用存儲到_中,而Go編譯器只會忽略它。

有時,如果要初始化一個包但不使用它,則用下劃線來給一個包起別名似乎什么也沒做是很有用的。

// parent.gopackage greetimport "fmt"var Message = "Hey there. I am parent."func init() {  fmt.Println("greet/parent.go ==> init()")}// child.gopackage greetimport "fmt"var Message = "Hey there. I am child."func init() {  fmt.Println("greet/greet/child.go ==> init()")}


要記住的主要事情是,每個包只對導入的包初始化一次。 因此,如果包中有任何import語句,則導入的包將在main包執行的生命周期中僅初始化一次。

如果我們使用.(點)作為類似import的別名。 " greet / greet",那么greet包的所有導出成員將在本地文件塊作用域中可用,我們可以在不使用限定符child的情況下引用Message。 因此,fmt.Println(Message)可以正常工作。 這種類型的導入被稱為"點導入" Go社區不是很喜歡它,因為它可能會引起一些問題。


程序執行順序

到目前為止,我們已經了解了有關軟件包的所有內容。 現在,讓我們結合對程序如何在Go中初始化的理解。

go run *.go├── Main package is executed├── All imported packages are initialized|  ├── All imported packages are initialized (recursive definition)|  ├── All global variables are initialized |  └── init functions are called in lexical file name order└── Main package is initialized   ├── All global variables are initialized   └── init functions are called in lexical file name order


這是一個證明這一點的小例子。

// version/get-version.gopackage versionimport "fmt"func init() { fmt.Println("version/get-version.go ==> init()")}func getVersion() string { fmt.Println("version/get-version.go ==> getVersion()") return "1.0.0"}/***************************/// version/entry.gopackage versionimport "fmt"func init() { fmt.Println("version/entry.go ==> init()")}var Version = getLocalVersion()func getLocalVersion() string { fmt.Println("version/entry.go ==> getLocalVersion()") return getVersion()}/***************************/// app/fetch-version.gopackage mainimport ( "fmt" "version")func init() { fmt.Println("app/fetch-version.go ==> init()")}func fetchVersion() string { fmt.Println("app/fetch-version.go ==> fetchVersion()") return version.Version}/***************************/// app/entry.gopackage mainimport "fmt"func init() { fmt.Println("app/entry.go ==> init()")}var myVersion = fetchVersion()func main() { fmt.Println("app/fetch-version.go ==> fetchVersion()") fmt.Println("version ===> ", myVersion)}



安裝第三方包

安裝第三方軟件包只不過是將遠程代碼克隆到本地src / 目錄中。 不幸的是,Go不支持程序包版本或不提供程序包管理器,但這正在等待提案。

(Go 1.3已經正式支持Go Modules 譯者注)

由于Go沒有統一的正式軟件包注冊中心,因此它會要求您提供軟件包的主機名和路徑。

$ go get -u github.com/jinzhu/gorm


上面的命令從http://github.com/jinzhu/gorm URL導入文件,并將其保存在src / github.com / jinzhu / gorm目錄中。 如嵌套軟件包中所述,您可以像下面那樣導入gorm軟件包。

package mainimport "github.com/jinzhu/gorm"// use ==> gorm.SomeExportedMember


因此,如果您制作了一個程序包并希望人們使用它,只需將其發布在GitHub上就可以了。 如果您的程序包是可執行的,則人們可以將其用作命令行工具,也可以將其導入程序中并將其用作實用程序模塊。 他們唯一需要做的就是使用以下命令。

$ go get github.com/your-username/repo-name


遷移到模塊Go Modules

Go提供了一個稱為Go Modules模塊的新功能,該功能提供了輕松管理項目和依賴項的靈活性。 如果您正在使用Go軟件包,則應考慮將項目重新定位到Go Modules。


(本文翻譯自Uday Hiwarale的文章《Everything you need to know about Packages in Go》,參考:https://medium.com/rungo/everything-you-need-to-know-about-packages-in-go-b8bac62b74cc)

(正文已結束)

推薦閱讀:手機充電寶品牌排行

免責聲明及提醒:此文內容為本網所轉載企業宣傳資訊,該相關信息僅為宣傳及傳遞更多信息之目的,不代表本網站觀點,文章真實性請瀏覽者慎重核實!任何投資加盟均有風險,提醒廣大民眾投資需謹慎!

網站簡介 - 聯系我們 - 營銷服務 - 老版地圖 - 版權聲明 - 網站地圖
Copyright.2002-2019 云南之窗 版權所有 本網拒絕一切非法行為 歡迎監督舉報 如有錯誤信息 歡迎糾正
饿了么商户赚钱 做短线股票 体彩浙江20选5中奖规则 广东好彩一开奖走势图 福建11选五玩法规则 天天彩选4走势连线图 贵州11选五开奖结果走势图 权重股票排名 河北省11选五 道琼斯工业股票指数 辽宁风采35选7