1 本地包聲明
創(chuàng)新互聯(lián)是一家專業(yè)提供衛(wèi)東企業(yè)網(wǎng)站建設(shè),專注與做網(wǎng)站、成都做網(wǎng)站、H5頁面制作、小程序制作等業(yè)務(wù)。10年已為衛(wèi)東眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計公司優(yōu)惠進(jìn)行中。
包是Go程序的基本單位,所以每個Go程序源代碼的開始都是一個包聲明:
package pkgName
這就是包聲明,pkgName 告訴編譯器,當(dāng)前文件屬于哪個包。一個包可以對應(yīng)多個*.go源文件,標(biāo)記它們屬于同一包的唯一依據(jù)就是這個package聲明,也就是說:無論多少個源文件,只要它們開頭的package包相同,那么它們就屬于同一個包,在編譯后就只會生成一個.a文件,并且存放在$GOPATH/pkg文件夾下。
示例:
(1) 我們在$GOPATH/目錄下,創(chuàng)建如下結(jié)構(gòu)的文件夾和文件:
分別寫入如下的代碼:
hello.go
//hello.go package hello import ( "fmt" ) func SayHello() { fmt.Println("SayHello()-->Hello") }
hello2.go
//hello2.go package hello import ( "fmt" ) func SayWorld() { fmt.Println("SayWorld()-->World") }
main.go
//main.go package main import ( "hello" ) func main() { hello.SayHello() hello.SayWorld() }
分析:
根據(jù)hello.go/hello2.go中的package聲明可知,它們倆屬于同一個包–hello,那么根據(jù)分析,編譯后只會生成一個*.a文件。
執(zhí)行命令:
go install hello
該命令的意思大概是:編譯并安裝hello包,這里安裝的意思是將生成的*.a文件放到工作目錄$GOPATH/pkg目錄下去
運行后:
從結(jié)果看出,果然只生成了一個包,并且名為hello.a
那么我們提出第二個問題:生成的*.a文件名是否就是我們定義的包名+.a后綴?
為了驗證這個問題,我們對源碼做一些更改:
將hello.go/hello2.go中的package聲明改為如下:
package hello_a
在編譯安裝包之前,先清除上一次生成的包:
go clean -i hello
再次編譯安裝該包:
go install hello_a
按照“正常推理”,上面這句命令是沒什么問題的,因為我們已經(jīng)將包名改成hello_a了啊,但是實際的運行結(jié)果是這樣的:
oh~No!!
那么,我們再試試用這條命令:
go install hello
臥槽!!居然成功了!!是不是??
那么我們嘗試生成一下可執(zhí)行程序,看看能不能正常運行呢?
go build main
又報錯了?。?!
看這報錯提示,好像應(yīng)該改一下main.go源碼,那就改成如下吧:
//main.go package main import ( "hello_a" ) func main() { hello_a.SayHello() hello_a.SayWorld() }
改成上面這樣也合情合理哈?畢竟我們把包名定義成了hello_a了!
那就再來編譯一次吧:
go build main
繼續(xù)報錯!
等等??!有新的發(fā)現(xiàn),對比上兩次的報錯信息,可見第一次還能能找到hello_a包的,更改源碼后居然還TM找不到hello_a包了??
好吧,那咱再改回去,不過這回只改包的導(dǎo)入語句,改成:
import ( "hello" )
再次編譯:
go build main
臥槽!!居然沒報錯了!!再運行一下可執(zhí)行程序:
好吧,終于得到了想要的結(jié)果!
那進(jìn)行到這里能說明什么呢?
(1) 一個包確實可以由多個源文件組成,只要它們開頭的包聲明一樣
(2)一個包對應(yīng)生成一個*.a文件,生成的文件名并不是包名+.a
(3) go install ××× 這里對應(yīng)的并不是包名,而是路徑名??!
(4) import ××× 這里使用的也不是包名,也是路徑名!
(5) ×××××.SayHello() 這里使用的才是包名!
那么問題又來了,我們該如何理解(3)、(4)中的路徑名呢?
我覺得,可以這樣理解:
這里指定的是該×××路徑名就代表了此目錄下唯一的包,編譯器連接器默認(rèn)就會去生成或者使用它,而不需要我們手動指明!
好吧,問題又來了,如果一個目錄下有多個包可以嗎?如果可以,那該怎么編譯和使用??
那我們繼續(xù)改改源代碼:
首先,保持hello2.go 不變,改動hello.go為如下代碼:
//hello.go package hello import ( "fmt" ) func SayHello() { fmt.Println("SayHello()-->Hello") }
并且更改main.go的源碼如下
//main.go package main import ( "hello" ) func main() { hello.SayHello() hello_a.SayWorld() }
再次清理掉上次生成的可執(zhí)行程序與包:
go clean -i hello go clean -x main
你可以試著執(zhí)行如上的命令,如果不能清除,那就手動刪除吧!
反正,還原成如下樣子:
那么再次嘗試編譯并安裝包,不過注意了,此時hello目錄下有兩個包了,不管是否正確,我們先嘗試一下:
go install hello
oh~~果然出錯了!!
看到了嗎?它說它找到了兩個包了?。。?!
那這能說明什么呢??
其實這就更加確定的說明了,我們上面的推測是正確的!
(3) go install ××× 這里對應(yīng)的并不是包名,而是路徑名??!
這里指定的是該×××路徑名就代表了此目錄下唯一的包,編譯器連接器默認(rèn)就會去生成或者使用它,而不需要我們手動指明!
好吧,證明了這個還是挺興奮的??!那我們繼續(xù)?。?/p>
如果一個目錄下,真的有兩個或者更多個包,那該如何生成??
抱著試一試的態(tài)度,我嘗試了許多可能,但無一正確,最后一個命令的結(jié)果是讓我崩潰的:
go help install
恩!對!你沒有看錯:installs the packages named by the import paths
What the fuck!! 以后還是決定要先看文檔再自己做測試??!
好吧,綜上所述,一個目錄下就只能有一個包吧,因為都是指定路徑,沒有辦法指定路徑下的某個具體的包,這樣的做法其實也挺好,讓源代碼結(jié)構(gòu)更清晰!
2 包的導(dǎo)入問題
導(dǎo)入包:
導(dǎo)入包的多種方式:
首先,還是對上面的示例程序做一個更改,這次我們讓它變得更加簡單點,因為接下來討論的東西,可能會稍微有點繞~~
首先,刪除hello2.go,清理掉編譯生成的文件,其他文件內(nèi)容如下:
hello.go
//hello.go package hello import ( "fmt" ) func SayHello() { fmt.Println("SayHello()-->Hello") }
main.go
//main.go package main import ( "hello" ) func main() { hello.SayHello() }
最后,讓整體保持如下的樣式:
我們先編譯一次,讓程序能夠運行起來:
go install hello go build main ./main
好吧,假如你能看到輸出,那就沒問題了!
此時,再來看看整體的結(jié)構(gòu):
按照C/C++的方式來說,此時生成了hello.a這個鏈接庫,那么源文件那些應(yīng)該就沒有必要了吧,所以。。。。我們這樣搞一下,我們來更改一下hello.go源碼,但不編譯它!
hello.go
//hello.go package hello import ( "fmt" ) func SayHello() { fmt.Println("SayHello()-->Hello_modifi...") }
然后,我們刪除之前的可執(zhí)行文件main,再重新生成它:
rm main go build main
恩~~等等,我看一下運行結(jié)果:
What the fuck!?。槭裁闯鰜淼氖沁@貨???
好吧,為了一探究竟,我們再次刪除main文件,并再次重新編譯,不過命令上得做點手腳,我們要看看編譯器連接器這兩個小婊砸到底都干了些什么,為啥是隔壁老王的兒子出來了????!
rm main go build -x -v main
結(jié)果:
那么我們一步一步對這個結(jié)果做一個分析:
#首先,它好像指定了一個臨時工作目錄 WORK=/tmp/go-build658882358 #看著樣子,它好像是要準(zhǔn)備編譯hello目錄下的包 hello #然后創(chuàng)建了一系列臨時文件夾 mkdir -p $WORK/hello/_obj/ mkdir -p $WORK/ #進(jìn)入包的源文件目錄 cd /home/yuxuan/GoProjects/import/src/hello #調(diào)用6g這個編譯器編譯生成hello.a,存放在$WORK/臨時目錄下 /opt/go/pkg/tool/linux_amd64/6g -o $WORK/hello.a -trimpath $WORK -p hello -complete -D _/home/yuxuan/GoProjects/import/src/hello -I $WORK -pack ./hello.go #要編譯main目錄下的包了 main #還是創(chuàng)建一系列的臨時文件夾 mkdir -p $WORK/main/_obj/ mkdir -p $WORK/main/_obj/exe/ #進(jìn)入main文件夾 cd /home/yuxuan/GoProjects/import/src/main #調(diào)用6g編譯器,編譯生成main.a,存放于$WORK/臨時目錄下 /opt/go/pkg/tool/linux_amd64/6g -o $WORK/main.a -trimpath $WORK -p main -complete -D _/home/yuxuan/GoProjects/import/src/main -I $WORK -I /home/yuxuan/GoProjects/import/pkg/linux_amd64 -pack ./main.go #最后它進(jìn)入了一個“當(dāng)前目錄”,應(yīng)該就是我們執(zhí)行g(shù)o build命令的目錄 cd . #調(diào)用連接器6l 然后它鏈接生成a.out,存放與臨時目錄下的$WORK/main/_obj/exe/文件夾中,但是在鏈接選項中并未直接發(fā)現(xiàn)hello.a #從鏈接選項:-L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64中可以看出,連接器首先搜索了$WORK臨時目錄下的所有*.a文件,然后再去搜索/home/yuxuan/GoProjects/import/pkg/linux_amd64目錄下的*.a文件,可見原因 /opt/go/pkg/tool/linux_amd64/6l -o $WORK/main/_obj/exe/a.out -L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64 -extld=gcc $WORK/main.a #最后,移動可執(zhí)行文件并重命名 mv $WORK/main/_obj/exe/a.out main
到這里,其實差不多也就得出結(jié)論了,連接器在連接時,其實使用的并不是我們工作目錄下的hello.a文件,而是以該最新源碼編譯出的臨時文件夾中的hello.a文件。
當(dāng)然,如果你對這個結(jié)論有所懷疑,可以試試手動執(zhí)行上述命令,在最后鏈接時,去掉-L $WORK的選項,再看看運行結(jié)果!
那么,這是對于有源代碼的第三方庫,如果沒有源代碼呢?
其實,結(jié)果顯而易見,沒有源代碼,上面的臨時編譯不可能成功,那么臨時目錄下就不可能有.a文件,所以最后鏈接時就只能鏈接到工作目錄下的.a文件!
但是,如果是自帶的Go標(biāo)準(zhǔn)庫呢?
其實也可以用上述的方法驗證一下,驗證過程就不寫了吧?
最后得到的結(jié)果是:對于標(biāo)準(zhǔn)庫,即便是修改了源代碼,只要不重新編譯Go源碼,那么鏈接時使用的就還是已經(jīng)編譯好的*.a文件!
3 導(dǎo)入包的三種模式
包導(dǎo)入有三種模式:正常模式、別名模式、簡便模式
Go language specification中關(guān)于import package時列舉的一個例子如下:
Import declaration Local name of Sin
import “l(fā)ib/math” math.Sin import m “l(fā)ib/math” m.Sin import . “l(fā)ib/math” Sin
我們看到import m “l(fā)ib/math” m.Sin一行,在上面的結(jié)論中說過lib/math是路徑,import語句用m替代lib/math,并在代碼中通過m訪問math包中導(dǎo)出的函數(shù)Sin。
那m到底是包名還是路徑呢?
答案顯而易見,能通過m訪問Sin,那m肯定是包名了!
那問題又來了,import m “l(fā)ib/math”該如何理解呢?
根據(jù)上面得出的結(jié)論,我們嘗試這樣理解m:m指代的是lib/math路徑下唯一的那個包!
4 總結(jié)
經(jīng)過上面這一長篇大論,是時候該總結(jié)一下成果了:
多個源文件可同屬于一個包,只要聲明時package指定的包名一樣;一個包對應(yīng)生成一個*.a文件,生成的文件名并不是包名+.a組成,應(yīng)該是目錄名+.a組成go install ××× 這里對應(yīng)的并不是包名,而是路徑名??!import ××× 這里使用的也不是包名,也是路徑名×××××.SayHello() 這里使用的才是包名!指定×××路徑名就代表了此目錄下唯一的包,編譯器連接器默認(rèn)就會去生成或者使用它,而不需要我們手動指明!一個目錄下就只能有一個包存在對于調(diào)用有源碼的第三方包,連接器在連接時,其實使用的并不是我們工作目錄下的.a文件,而是以該最新源碼編譯出的臨時文件夾中的.a文件對于調(diào)用沒有源碼的第三方包,上面的臨時編譯不可能成功,那么臨時目錄下就不可能有.a文件,所以最后鏈接時就只能鏈接到工作目錄下的.a文件對于標(biāo)準(zhǔn)庫,即便是修改了源代碼,只要不重新編譯Go源碼,那么鏈接時使用的就還是已經(jīng)編譯好的*.a文件包導(dǎo)入有三種模式:正常模式、別名模式、簡便模式
到此這篇關(guān)于Golang import本地包和導(dǎo)入問題相關(guān)詳解的文章就介紹到這了,更多相關(guān)Golang import包內(nèi)容請搜索創(chuàng)新互聯(lián)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持創(chuàng)新互聯(lián)!