go 调用其他文件函数_一文读懂Go中软件包概念
Go編程語(yǔ)言的軟件包管理和部署的完整概述
如果您熟悉Java或NodeJS之類的語(yǔ)言,那么您可能非常熟悉軟件包。 包不過(guò)是帶有一些代碼文件的目錄,該目錄從單個(gè)引用點(diǎn)公開(kāi)了不同的變量(功能)。 讓我解釋一下,這意味著什么。
想象一下,在任何項(xiàng)目中進(jìn)行工作時(shí),您都需要不斷地使用上千種功能。 其中一些功能具有共同的行為。 例如,toUpperCase和toLowerCase函數(shù)可轉(zhuǎn)換字符串的大小寫(xiě),因此您可以將它們寫(xiě)入單個(gè)文件(可能為case.go)。 還有其他函數(shù)對(duì)字符串?dāng)?shù)據(jù)類型執(zhí)行其他一些操作,因此您也將它們寫(xiě)在單獨(dú)的文件中。
由于您有許多文件可以處理字符串?dāng)?shù)據(jù)類型,因此您創(chuàng)建了一個(gè)名為string的目錄,并將所有與字符串相關(guān)的文件放入其中。 最后,將所有這些目錄放在一個(gè)父目錄中,該目錄將成為您的軟件包。 整個(gè)程序包結(jié)構(gòu)如下所示。
package-name├── string| ├── case.go| ├── trim.go| └── misc.go└── number ├── arithmetics.go └── primes.go我將詳細(xì)解釋如何從包中導(dǎo)入函數(shù)和變量,以及如何將所有內(nèi)容融合在一起形成一個(gè)包,但是現(xiàn)在,將您的包想象成一個(gè)包含.go文件的目錄。
每個(gè)Go程序都必須是某些程序包的一部分。 如Go入門(mén)課程中所述,獨(dú)立的可執(zhí)行Go程序必須具有包main聲明。 如果程序是主軟件包的一部分,則go install將創(chuàng)建一個(gè)二進(jìn)制文件; 在執(zhí)行時(shí)調(diào)用程序的主要功能。 如果程序不是main包的一部分,則使用go install命令創(chuàng)建包庫(kù)文件。 不用擔(dān)心,我將在以后的主題中解釋所有這些內(nèi)容。
讓我們創(chuàng)建一個(gè)可執(zhí)行程序包。 眾所周知,要?jiǎng)?chuàng)建一個(gè)二進(jìn)制可執(zhí)行文件,我們需要程序成為主程序包的一部分,并且它必須具有main函數(shù),該函數(shù)是執(zhí)行的入口點(diǎn)。
軟件包名稱是src目錄中包含的目錄的名稱。 在上述情況下,因?yàn)閍pp是src目錄的子目錄,所以app是軟件包。 因此,執(zhí)行g(shù)o install app命令在GOPATH的src目錄中查找app子目錄。 然后,它編譯了程序包,并在bin目錄中創(chuàng)建了應(yīng)用二進(jìn)制可執(zhí)行文件,該文件應(yīng)可從終端執(zhí)行,因?yàn)镻ATH中的bin目錄。
像上面示例中的package main應(yīng)該是代碼的第一行的包聲明,它可以與包名稱不同。 因此,您可能會(huì)發(fā)現(xiàn)某些軟件包,其中軟件包名稱(目錄名稱)與軟件包聲明不同。 導(dǎo)入程序包時(shí),程序包聲明用于創(chuàng)建程序包引用變量,如本文稍后所述。
go install 命令在給定的軟件包目錄中查找具有主軟件包聲明的任何文件。 如果找到文件,則Go知道這是一個(gè)可執(zhí)行程序,因此需要?jiǎng)?chuàng)建一個(gè)二進(jìn)制文件。 一個(gè)包可以有許多文件,但只有一個(gè)具有主要功能的文件,因?yàn)樵撐募⑹菆?zhí)行的入口。
如果軟件包不包含帶有main軟件包聲明的文件,則Go會(huì)在pkg目錄中創(chuàng)建一個(gè)軟件包庫(kù)0(.a)文件。
由于app不是可執(zhí)行程序包,因此它在pkg目錄中創(chuàng)建了app.a文件。 該文件不是二進(jìn)制文件,因此無(wú)法執(zhí)行。
包命名約定
Go社區(qū)建議對(duì)軟件包使用簡(jiǎn)單的名稱。 例如,用于字符串實(shí)用程序功能的strutils,或用于HTTP請(qǐng)求相關(guān)功能的http。 應(yīng)該避免使用下劃線,連字符或大小寫(xiě)混合的軟件包名稱。
創(chuàng)建一個(gè)包
正如我們所討論的,有兩種類型的軟件包。 可執(zhí)行程序包和實(shí)用程序包。 可執(zhí)行包是您的主要應(yīng)用程序,因?yàn)槟鷮⒃谶\(yùn)行它。 實(shí)用程序包不是自可執(zhí)行的,相反,它通過(guò)提供實(shí)用程序功能和其他重要資產(chǎn)來(lái)增強(qiáng)可執(zhí)行程序包的功能。
眾所周知,一個(gè)包只不過(guò)是一個(gè)目錄,讓我們?cè)趕rc中創(chuàng)建greet目錄,并在其中創(chuàng)建幾個(gè)文件。 這次,我們將在每個(gè)文件的頂部編寫(xiě)一個(gè)包聲明 package greet,以聲明這是一個(gè)實(shí)用程序包。
導(dǎo)出成員
實(shí)用程序包應(yīng)該為導(dǎo)入它的包提供一些變量。 與JavaScript中的 export 語(yǔ)法一樣,如果變量名稱以大寫(xiě)字母開(kāi)頭,則Go會(huì)導(dǎo)出變量。 所有其他不以大寫(xiě)字母開(kāi)頭的變量對(duì)程序包都是私有的。
從現(xiàn)在開(kāi)始,我將使用 variable 來(lái)描述導(dǎo)出成員,但是導(dǎo)出成員可以是常量,映射,函數(shù),結(jié)構(gòu),數(shù)組,切片等任何類型。
讓我們從day.go文件中導(dǎo)出一個(gè)問(wèn)候變量。
在上面的程序中,Morning變量將從包中導(dǎo)出,但是morning變量不會(huì),它以小寫(xiě)字母開(kāi)頭,因此不會(huì)。
導(dǎo)入包
現(xiàn)在,我們需要一個(gè)可執(zhí)行包,它將使用我們的greet包。 讓我們?cè)趕rc內(nèi)創(chuàng)建一個(gè)app目錄,并創(chuàng)建帶有main包聲明和main函數(shù)的entry.go文件。 請(qǐng)注意,Go包沒(méi)有像在Node中index.js這樣的入口文件命名系統(tǒng)。 對(duì)于可執(zhí)行程序包,具有main功能的文件是要執(zhí)行的入口文件。
要導(dǎo)入軟件包,我們使用import語(yǔ)法,后跟軟件包名稱。
與其他編程語(yǔ)言不同,包名稱也可以是諸如some-dir / greet的子路徑,而Go會(huì)自動(dòng)為我們解析到greet包的路徑,如前面的嵌套包主題所示。
首先轉(zhuǎn)到GOROOT / src目錄中的包目錄,如果找不到包,則查找GOPATH / src。 由于fmt軟件包是Go的標(biāo)準(zhǔn)庫(kù)(位于GOROOT / src中)的一部分,因此從那里導(dǎo)入。 由于Go無(wú)法在GOROOT中找到greet包,因此它將在GOPATH / src中進(jìn)行查找,并且我們已經(jīng)在其中了。
上面的程序拋出編譯錯(cuò)誤,因?yàn)閺陌锌床坏絤orning變量。 如您所見(jiàn),我們使用. (點(diǎn))表示法,用于從包中訪問(wèn)導(dǎo)出的成員。 導(dǎo)入程序包時(shí),Go使用程序包的程序包聲明創(chuàng)建一個(gè)全局變量。 在上述情況下,greet是Go創(chuàng)建的全局變量,因?yàn)槲覀冊(cè)趃reet軟件包中包含的程序中使用了package greet聲明。
我們可以使用分組語(yǔ)法(括號(hào))將fmt和greet包一起導(dǎo)入。 這次,我們的程序?qū)⒕幾g得很好,因?yàn)榭梢詮陌獠揩@得Morning變量。
嵌套包
我們可以將包嵌套在包中。 由于對(duì)于Go,包只是目錄,就像在現(xiàn)有包中創(chuàng)建子目錄一樣。 我們要做的就是提供嵌套包的相對(duì)路徑。
包編譯
如上一課所述,go run命令編譯并執(zhí)行程序。 我們知道,go install命令可以編譯軟件包并創(chuàng)建二進(jìn)制可執(zhí)行文件或軟件包庫(kù)文件。 這是為了避免每次編譯(導(dǎo)入這些程序包的程序)每次都編譯程序包。 go install會(huì)預(yù)編譯一個(gè)軟件包,而Go則引用.a文件。
通常,當(dāng)您安裝第三方軟件包時(shí),Go會(huì)編譯該軟件包并創(chuàng)建軟件包存檔文件。 如果您在本地編寫(xiě)軟件包,則IDE可能會(huì)在將文件保存在軟件包中或在軟件包被修改后立即創(chuàng)建軟件包庫(kù)文件。 如果已安裝Go插件,則VSCode在保存時(shí)會(huì)編譯該軟件包。
包初始化
當(dāng)我們運(yùn)行Go程序時(shí),Go編譯器會(huì)按照一定的執(zhí)行順序執(zhí)行程序包,程序包中的文件以及程序包中的變量聲明。
包范圍
范圍是代碼塊中可訪問(wèn)定義變量的區(qū)域。 包作用域是包中的一個(gè)區(qū)域,在該區(qū)域中可從包內(nèi)(包中的所有文件)訪問(wèn)聲明的變量。 此區(qū)域是軟件包中任何文件的最上面的塊。
看一下go run命令。 這次,我們不再使用一個(gè)文件,而是使用一種全局模式將所有文件都包含在應(yīng)用程序包中以便執(zhí)行。 Go具有足夠的智能,可以找出應(yīng)用程序的0=6圖片;v'entry.go入口點(diǎn),因?yàn)樗哂衜ain函數(shù)。 我們還可以使用如下所示的命令(文件名順序無(wú)關(guān)緊要)。
go run src/app/version.go src/app/entry.gogo install或go build命令需要一個(gè)軟件包名稱,其中包括軟件包中的所有文件,因此我們不必像上面那樣指定它們。
回到我們的主要問(wèn)題,我們可以在包的任何地方使用在version.go文件中聲明的變量version,即使它沒(méi)有導(dǎo)出(如Version),因?yàn)樗窃诎秶鷥?nèi)聲明的。 如果在函數(shù)中聲明了version變量,則說(shuō)明該version 變量不在包范圍內(nèi),并且上述程序無(wú)法編譯。
不允許在同一包中重新聲明具有相同名稱的全局變量。 因此,一旦聲明了版本變量,就不能在程序包范圍內(nèi)對(duì)其進(jìn)行重新聲明。 但是您可以隨意在其他地方重新聲明。
變量初始化
當(dāng)變量a依賴于另一個(gè)變量b時(shí),應(yīng)事先定義b,否則程序?qū)o(wú)法編譯。 Go在函數(shù)內(nèi)部遵循此規(guī)則。
但是,當(dāng)這些變量在包范圍內(nèi)定義時(shí),它們將在初始化周期中聲明。 讓我們看看下面的簡(jiǎn)單示例。
在上面的示例中,第一個(gè)c被聲明,因?yàn)樗闹狄呀?jīng)被聲明。 在以后的初始化周期中,聲明了b,因?yàn)樗Q于c并且已經(jīng)聲明了c的值。 在最后的初始化周期中,聲明a并將其分配給b的值。 Go可以處理如下所示的復(fù)雜初始化周期。
在上面的示例中,將首先聲明c,然后聲明b,因?yàn)槠渲等Q于c,最后是a,因?yàn)槠渲等Q于b。 您應(yīng)該避免像下面這樣的初始化循環(huán)。
包范圍的另一個(gè)示例是,在單獨(dú)文件中具有函數(shù)f,該函數(shù)從主文件引用變量c。
初始化函數(shù)
像main函數(shù)一樣,初始化包時(shí),Go會(huì)調(diào)用init函數(shù)。 它不帶任何參數(shù),也不返回任何值。 Go隱式聲明了init函數(shù),因此您無(wú)法從任何地方引用它(或像init()一樣調(diào)用它)。 您可以在文件或包中聲明多個(gè)init函數(shù)。 文件中init函數(shù)的執(zhí)行順序?qū)⒏鶕?jù)它們出現(xiàn)的順序。
您可以在包中的任何位置聲明init函數(shù)。 這些初始化函數(shù)以文件名字母順序調(diào)用。
畢竟,要執(zhí)行init函數(shù),然后才能調(diào)用main函數(shù)。 因此,init函數(shù)的主要工作是初始化無(wú)法在全局上下文中初始化的全局變量。 例如,數(shù)組的初始化。
由于for語(yǔ)法在包范圍內(nèi)無(wú)效,因此我們可以在init函數(shù)內(nèi)部使用for循環(huán)來(lái)初始化大小為10的數(shù)組整數(shù)。
包別名
導(dǎo)入包時(shí),請(qǐng)使用包的包聲明創(chuàng)建一個(gè)變量。 如果要導(dǎo)入多個(gè)具有相同名稱的軟件包,則將導(dǎo)致沖突。
// parent.gopackage greetvar Message = "Hey there. I am parent."// child.gopackage greetvar Message = "Hey there. I am child."因此,我們使用包別名。 我們?cè)趇mport關(guān)鍵字和程序包名稱之間聲明一個(gè)變量名,該變量名成為引用該程序包的新變量。
在上面的示例中,greet / greet包現(xiàn)在由子變量引用。 如果您注意到的話,我們?cè)趃reet包中使用下劃線作為別名。 下劃線是Go中的一個(gè)特殊字符,用作空容器。 由于我們導(dǎo)入的是greet包,但未使用它,因此Go編譯器會(huì)抱怨它。 為了避免這種情況,我們將對(duì)該包的引用存儲(chǔ)到_中,而Go編譯器只會(huì)忽略它。
有時(shí),如果要初始化一個(gè)包但不使用它,則用下劃線來(lái)給一個(gè)包起別名似乎什么也沒(méi)做是很有用的。
// 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()")}要記住的主要事情是,每個(gè)包只對(duì)導(dǎo)入的包初始化一次。 因此,如果包中有任何import語(yǔ)句,則導(dǎo)入的包將在main包執(zhí)行的生命周期中僅初始化一次。
如果我們使用.(點(diǎn))作為類似import的別名。 " greet / greet",那么greet包的所有導(dǎo)出成員將在本地文件塊作用域中可用,我們可以在不使用限定符child的情況下引用Message。 因此,fmt.Println(Message)可以正常工作。 這種類型的導(dǎo)入被稱為"點(diǎn)導(dǎo)入" Go社區(qū)不是很喜歡它,因?yàn)樗赡軙?huì)引起一些問(wèn)題。
程序執(zhí)行順序
到目前為止,我們已經(jīng)了解了有關(guān)軟件包的所有內(nèi)容。 現(xiàn)在,讓我們結(jié)合對(duì)程序如何在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這是一個(gè)證明這一點(diǎn)的小例子。
// 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)}安裝第三方包
安裝第三方軟件包只不過(guò)是將遠(yuǎn)程代碼克隆到本地src / 目錄中。 不幸的是,Go不支持程序包版本或不提供程序包管理器,但這正在等待提案。
(Go 1.3已經(jīng)正式支持Go Modules 譯者注)
由于Go沒(méi)有統(tǒng)一的正式軟件包注冊(cè)中心,因此它會(huì)要求您提供軟件包的主機(jī)名和路徑。
$ go get -u github.com/jinzhu/gorm上面的命令從http://github.com/jinzhu/gorm URL導(dǎo)入文件,并將其保存在src / github.com / jinzhu / gorm目錄中。 如嵌套軟件包中所述,您可以像下面那樣導(dǎo)入gorm軟件包。
package mainimport "github.com/jinzhu/gorm"// use ==> gorm.SomeExportedMember因此,如果您制作了一個(gè)程序包并希望人們使用它,只需將其發(fā)布在GitHub上就可以了。 如果您的程序包是可執(zhí)行的,則人們可以將其用作命令行工具,也可以將其導(dǎo)入程序中并將其用作實(shí)用程序模塊。 他們唯一需要做的就是使用以下命令。
$ go get github.com/your-username/repo-name遷移到模塊Go Modules
Go提供了一個(gè)稱為Go Modules模塊的新功能,該功能提供了輕松管理項(xiàng)目和依賴項(xiàng)的靈活性。 如果您正在使用Go軟件包,則應(yīng)考慮將項(xiàng)目重新定位到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)
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的go 调用其他文件函数_一文读懂Go中软件包概念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 哪款浏览器好用_碉堡了!火狐浏览器发布重
- 下一篇: hive一次加载多个文件_0738-6.