日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(3)— 包概念、包特点、包名约束、main 包、包的声明、包的引用、包初始化

發(fā)布時(shí)間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(3)— 包概念、包特点、包名约束、main 包、包的声明、包的引用、包初始化 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 包的概念

Go 語言是使用包來組織源代碼的,并實(shí)現(xiàn)命名空間的管理。任何源代碼文件必須屬于某個(gè)包。源碼文件的第一行有效代碼必須是 package pacakgeName 語句,通過該語句聲明自己所在的包。

2. 包的特征

所有的 .go 文件,除了空行和注釋,都應(yīng)該在第一行聲明自己所屬的包。即所有代碼都必須組織在 package 中。包的結(jié)構(gòu)特點(diǎn)有:

  • 源文件頭部以 package 聲明包名稱;
  • 包由同一目錄下的多個(gè)源碼文件組成,即一個(gè)目錄下的同級(jí)文件屬于同一個(gè)包;
  • 每個(gè)包都在一個(gè)單獨(dú)的目錄里;
  • 包所在的目錄名最好不用 mainall 、 std 這三個(gè)保留名稱;
  • 可執(zhí)行文件必須包含 package main 和入口函數(shù) main main 包是 Go 語言程序的入口包,一個(gè) Go 語言程序必須有且僅有一個(gè) main 包,并且,一個(gè) main 包中也必須有且僅有一個(gè) main 函數(shù)。如果一個(gè)程序沒有 main 包,那么編譯時(shí)將會(huì)出錯(cuò),無法生成可執(zhí)行文件;
  • 不能把多個(gè)包放到同一個(gè)目錄中,也不能把同一個(gè)包的文件分拆到多個(gè)不同目錄中。這意味著,同一個(gè)目錄下的所有 .go 文件必須聲明同一個(gè)包名;

包中成員以名稱首字母大小寫決定訪問權(quán)限。

  • Public : 首字母大寫,可被包外訪問;
  • internal : 首字母小寫,僅包內(nèi)成員可以訪問;

該規(guī)則適用于全局變量、全局常量、類型、結(jié)構(gòu)字段、函數(shù)、方法等。

3. 包名約束

給包命名的慣例是使用包所在目錄的名字。給包及其目錄命名時(shí),應(yīng)該使用簡潔、清晰且全小寫的名字,這有利于開發(fā)時(shí)頻繁輸入包名。

記住,并不需要所有包的名字都與別的包不同,因?yàn)閷?dǎo)入包時(shí)是使用全路徑的,所以可以區(qū)分同名的不同包。一般情況下,包被導(dǎo)入后會(huì)使用你的包名作為默認(rèn)的名字,不過這個(gè)導(dǎo)入后的名字可以修改。這個(gè)特性在需要導(dǎo)入不同目錄的同名包時(shí)很有用。

關(guān)于默認(rèn)包名一般采用導(dǎo)入路徑名的最后一段的約定也有三種例外情況。

  1. 包對應(yīng)一個(gè)可執(zhí)行程序,也就是 main 包,這時(shí)候 main 包本身的導(dǎo)入路徑是無關(guān)緊要的。名字為 main 的包是給 go build 構(gòu)建命令一個(gè)信息,這個(gè)包編譯完之后必須調(diào)用連接器生成一個(gè)可執(zhí)行程序。
  2. 包所在的目錄中可能有一些文件名是以 _test.go為后綴的 Go 源文件(譯注:前面必須有其它的字符,因?yàn)橐?_.開頭的源文件會(huì)被構(gòu)建工具忽略),并且這些源文件聲明的包名也是以 _test為后綴名的。這種目錄可以包含兩種包:一種是普通包,另一種則是測試的外部擴(kuò)展包。所有以 _test為后綴包名的測試外部擴(kuò)展包都由 go test 命令獨(dú)立編譯,普通包和測試的外部擴(kuò)展包是相互獨(dú)立的。測試的外部擴(kuò)展包一般用來避免測試代碼中的循環(huán)導(dǎo)入依賴。
  3. 一些依賴版本號(hào)的管理工具會(huì)在導(dǎo)入路徑后追加版本號(hào)信息,例如“gopkg.in/yaml.v2” 這種情況下包的名字并不包含版本號(hào)后綴,而是 yaml 。

4. main 包

Go 語言里,命名為 main 的包具有特殊的含義。 Go 語言的編譯程序會(huì)試圖把這種名字的包編譯為二進(jìn)制可執(zhí)行文件。所有用 Go 語言編譯的可執(zhí)行程序都必須有一個(gè)名叫 main 的包。

當(dāng)編譯器發(fā)現(xiàn)某個(gè)包的名字為 main 時(shí),它一定也會(huì)發(fā)現(xiàn)名為 main() 的函數(shù),否則不會(huì)創(chuàng)建可執(zhí)行文件。 main() 函數(shù)是程序的入口,所以,如果沒有這個(gè)函數(shù),程序就沒有辦法開始執(zhí)行。

程序編譯時(shí),會(huì)使用聲明 main 包代碼所在目錄的目錄名作為二進(jìn)制可執(zhí)行文件的文件名。

而且通常來說,main 包應(yīng)該很簡潔。我們在 main 包中會(huì)做一些命令行參數(shù)解析、資源初始化、日志設(shè)施初始化、數(shù)據(jù)庫連接初始化等工作,之后就會(huì)將程序的執(zhí)行權(quán)限交給更高級(jí)的執(zhí)行控制對象。

5. 包的聲明

Go 語言中,代碼包中的源碼文件名可以是任意的,這些任意名稱的源碼文件都必須以包聲明語句作為文件中代碼的第一行。比如 src 目錄下的代碼包 common/upload 包中的所有源碼文件都要先聲明自己屬于common/upload 包:

package upload

packageGo 語言中用于包聲明語句的關(guān)鍵字。 Go 語言規(guī)定包聲明中的包名為代碼包路徑的最后一個(gè)元素。如上,common/upload 包的包路徑為 common/upload ,而包聲明中的包名則為 upload 。

而針對命令源碼文件(即包含 main 函數(shù)的 .go 文件),無論存放在哪個(gè)包中,它都必須聲明為屬于 main。

6. 包的引用

標(biāo)準(zhǔn)包的源碼位于 $GOROOT/src/ 下面,標(biāo)準(zhǔn)包可以直接引用。自定義的包和第三方包的源碼必須放到 $GOPATH/src/ 目錄下才能被引用。導(dǎo)入包需要使用關(guān)鍵字 import ,它會(huì)告訴編譯器你想引用該位置的包內(nèi)的代碼。如果需要導(dǎo)入多個(gè)包,習(xí)慣上是將 import 語句包裝在一個(gè)導(dǎo)入塊中。

包的引用路徑有兩種寫法, 一種是絕對路徑,另一種是相對路徑。

要在代碼中引用其他包的內(nèi)容,需要使用 import 關(guān)鍵字導(dǎo)入使用的包。具體語法如下:

import "包的路徑"

注意事項(xiàng):

  • import 導(dǎo)入語句通常放在源碼文件開頭包聲明語句的下面;
  • 導(dǎo)入的包名需要使用雙引號(hào)包裹起來;
  • 包名是從GOPATH/src/后開始計(jì)算的,使用/進(jìn)行路徑分隔。

包的導(dǎo)入有兩種寫法,分別是單行導(dǎo)入和多行導(dǎo)入。

  • 單行導(dǎo)入:
import "包 1 的路徑"
import "包 2 的路徑"
  • 多行導(dǎo)入:
import ("包 1 的路徑""包 2 的路徑"
)

6.1 絕對路徑引用

包的絕對路徑就是 $GOROOT/src$GOPATH/src 后面包的源碼的全路徑,比如下面的包引用:

import "common/upload"
import "database/sql/driver"
import "database/sql"

upload 包是自定義的包,其源碼位于 $GOPATH/src/common/upload 目錄下,代碼包導(dǎo)入使用的路徑就是代碼包在工作區(qū)的 src 目錄下的相對路徑,比如 upload 的絕對路徑為 /home/wohu/gocode/src/common/upload ,而 /home/wohu/gocode 是被包含在環(huán)境變量 GOPATH 中的工作區(qū)目錄路徑,則其代碼包導(dǎo)入路徑就是common/upload。

sqldriver 包的源碼分別位于 $GOROOT/src/database/sql$GOROOT/src/database/sql/driver 下。

編譯器會(huì)首先查找 Go 的安裝目錄,然后才會(huì)按順序查找 GOPATH 變量里列出的目錄。一旦編譯器找到一個(gè)滿足 import 語句的包,就停止進(jìn)一步查找。

6.2 相對路徑引用


相對路徑只能用于引用 $GOPATH 下的包,標(biāo)準(zhǔn)包的引用只能使用全路徑引用。比如下面兩個(gè)包:
a 的路徑是 $GOPATH/src/lab/a ,包 b 的源碼路徑為 $GOPATH/src/lab/b ,假設(shè) b 引用了 a 包,則可以使用相對路徑引用方式。示例如下:

// 相對路徑引用
import "../a" // 絕對路徑引用
import "lab/a"

6.3 引用格式

常用的包引用有以下 4 種格式,我們以 fmt 包為例進(jìn)行說明。

  1. 標(biāo)準(zhǔn)引用方式
import "fmt”

此時(shí)可以用 fmt. 作為前綴引用包內(nèi)可導(dǎo)出元素,這是常用的一種方式。

  1. 別名引用方式
import F "fmt”

此時(shí)相當(dāng)于給包 fmt 起了個(gè)別名 F ,用 F.代替標(biāo)準(zhǔn)的 fmt.作為前綴引用 fmt 包內(nèi)可導(dǎo)出元素。

  1. 省略引用方式
import . "fmt"

此時(shí)相當(dāng)于把包 fmt 的命名空間直接合并到當(dāng)前程序的命名空間中,使用 fmt 包內(nèi)可導(dǎo)出元素可以不用前綴 fmt. ,直接引用。示例如下:

package main
import . "fmt"
func main() {// 不需要加前級(jí)fmt.Println("hello , world”)
}
  1. 僅執(zhí)行包初始化 init 函數(shù)

使用標(biāo)準(zhǔn)格式引用包,但是代碼中卻沒有使用包,編譯器會(huì)報(bào)錯(cuò)。如果包中有 init 初始化函數(shù),則通過 import packageName 這種方式引用包,僅執(zhí)行包的初始化函數(shù),即使包沒有 init 初始化函數(shù),也不會(huì)引發(fā)編譯器報(bào)錯(cuò)。示例如下:

import  _ "fmt"

下劃線字符 _Go 語言里稱為空白標(biāo)識(shí)符,這個(gè)標(biāo)識(shí)符用來拋棄不想繼續(xù)使用的值,如給導(dǎo)入的包賦予一個(gè)空名字,或者忽略函數(shù)返回的你不感興趣的值。

  1. 遠(yuǎn)程導(dǎo)入

Go 工具鏈會(huì)使用導(dǎo)入路徑確定需要獲取的代碼在網(wǎng)絡(luò)的什么地方。

import "github.com/net/http"

用導(dǎo)入路徑編譯程序時(shí), go build 命令會(huì)使用 GOPATH 的設(shè)置,在磁盤上搜索這個(gè)包。

事實(shí)上,這個(gè)導(dǎo)入路徑代表一個(gè) URL ,指向 GitHub 上的代碼庫。如果路徑包含 URL ,可以使用 Go 工具鏈從 分布式版本控制系統(tǒng)獲取包,并把包的源代碼保存在 GOPATH 指向的路徑里與 URL 匹配的目錄里。

這個(gè)獲取過程使用 go get 命令完成。go get 將獲取任意指定的 URL 的包,或者一個(gè)已經(jīng)導(dǎo)入的包所依賴的其它包。由于 go get 的這種遞歸特性,這個(gè)命令會(huì)掃描某個(gè)包的源碼樹,獲取能找到的所有依賴包。

6.4 綜合實(shí)踐

當(dāng)導(dǎo)入多個(gè)代碼包時(shí),需要用圓括號(hào)括起它們,且每個(gè)代碼包名獨(dú)占一行。在調(diào)用被導(dǎo)入代碼包中的函數(shù)或使用其中的結(jié)構(gòu)體、變量或常量時(shí),需要使用包路徑的最后一個(gè)元素加 . 的方式指定代碼所在的包。

例如,如果我們有兩個(gè)包 logginggo_lib/logging , 并且有相同的方法 logging_print() ,且有一個(gè)源碼文件需要導(dǎo)入這兩個(gè)包(標(biāo)準(zhǔn)引用):

import ("logging""go_lib/logging"
)

則這句代碼 logging.logging_print() 就會(huì)引起沖突, Go 語言無法知道 logging. 代表的是哪一個(gè)包。所以,在 Go 語言中,如果在同一個(gè)源碼文件中使用上述方法導(dǎo)入多個(gè)代碼包,那么代碼包路徑的最后一個(gè)元素不可以重復(fù)。

如果用這段代碼包導(dǎo)入代碼,在編譯代碼時(shí),Go 語言會(huì)拋出

”logging redeclared as imported package name”

的錯(cuò)誤。如果確實(shí)需要導(dǎo)入,當(dāng)有這類重復(fù)時(shí),我們可以給它們起個(gè)別名來區(qū)別(別名引用):

import (la "logging"lb "go_lib/logging"
)

調(diào)用包中的代碼:

var logger la.Logger = la.logging_print()

這里不必給每個(gè)引起沖突的代碼包都起一個(gè)別名,只要能夠區(qū)分它們就可以了。

如果我們想直接調(diào)用某個(gè)依賴包的程序,就可以用 . 來代替別名(省略引用)。

import (. "logging"lb "go_lib/logging"
)

在當(dāng)前源碼文件中,可以直接進(jìn)行代碼調(diào)用了:

var logger Logger = logging_print()

Go 語言把變量、常量、函數(shù)、結(jié)構(gòu)體和接口統(tǒng)稱為程序?qū)嶓w,而把它們的名字統(tǒng)稱為標(biāo)識(shí)符。標(biāo)識(shí)符可以是任何 Unicode 編碼可以表示的字母字符、數(shù)字以及下劃線 ”_”,并且,首字母不能是數(shù)字。標(biāo)識(shí)符的首字母的大小寫控制著對應(yīng)程序?qū)嶓w的訪問權(quán)限。

如果標(biāo)識(shí)符的首字母是大寫的,那么它對應(yīng)的程序?qū)嶓w就可以被本代碼包之外的代碼訪問到,也可以稱其為可導(dǎo)出的。否則對應(yīng)的程序?qū)嶓w就只能被本包內(nèi)的代碼訪問。當(dāng)然,還需要有以下兩個(gè)額外條件:

  • (1)、程序?qū)嶓w必須是非局部的。局部程序?qū)嶓w是被定義在函數(shù)或結(jié)構(gòu)體的內(nèi)部。
  • (2)、代碼包所在的目錄必須被包含在環(huán)境變量 GOPATH 中的工作區(qū)目錄中。

如果代碼包 logging 中有一個(gè)叫做 getSimpleLogger 的函數(shù),那么光從這個(gè)函數(shù)的名字上我們就可以看出,這個(gè)函數(shù)是不能被包外代碼調(diào)用的。

如果我們只想初始化某個(gè)代碼包而不需要在當(dāng)前源碼文件中使用那個(gè)代碼包中的任何代碼,即可以用 _ 來代替別名(僅執(zhí)行包初始化 init 函數(shù)的引用方式)。

import (_ "logging"
)

6.5 注意事項(xiàng)

  1. 一個(gè)包可以有多個(gè) init 函數(shù),包加載會(huì)執(zhí)行全部的 init 函數(shù),但并不能保證執(zhí)行順序,所以不建議在一個(gè)包中放入多個(gè) init 函數(shù),將需要初始化的邏輯放到一個(gè) init 函數(shù)里面。
  2. 包不能出現(xiàn)循環(huán)引用。比如包 a 引用了包 b ,包 b 引用了包 c,如果包 c 又引用了包 a,則編譯不能通過。
  3. 包的重復(fù)引用是允許的。比如包 a 引用了包 b 和包 c ,包 b 和包 c 都引用了包 d 。這種場景相當(dāng)于重復(fù)引用了d,這種情況是允許的, 并且 Go 編譯器保證 d 的 init 函數(shù)只會(huì)執(zhí)行一次。

7. 包初始化

Go 語言中,可以有專門的函數(shù)負(fù)責(zé)代碼包初始化。這個(gè)函數(shù)需要無參數(shù)聲明和結(jié)果聲明,且名稱必須為 init ,如下:

func init() {println("Initialize")
}

Go 語言會(huì)在程序真正執(zhí)行前對整個(gè)程序的依賴進(jìn)行分析,并初始化相關(guān)的代碼包。也就是說,所有的代碼包初始化函數(shù)都會(huì)在 main 函數(shù)(命令源碼文件中的入口函數(shù))之前執(zhí)行完成,而且只會(huì)執(zhí)行一次。并且,當(dāng)前代碼包中的所有全局變量的初始化都會(huì)在代碼包初始化函數(shù)執(zhí)行前完成。這就避免了在代碼包初始化函數(shù)對某個(gè)變量進(jìn)行賦值之后又被該變量聲明中賦予的值覆蓋掉的問題。

每個(gè)包可以包含任意多個(gè) init 函數(shù),這些函數(shù)都會(huì)在程序執(zhí)行開始的時(shí)候被調(diào)用。所有被編譯器發(fā)現(xiàn)的 init 函數(shù)都會(huì)安排在 main 函數(shù)之前執(zhí)行。 init 函數(shù)用在設(shè)置包、初始化變量或者其他要在程序運(yùn)行前優(yōu)先完成的引導(dǎo)工作。

Go 里面有兩個(gè)保留的函數(shù): init 函數(shù)(能夠應(yīng)用于所有的 package )和 main 函數(shù)(只能應(yīng)用于 package main )。這兩個(gè)函數(shù)在定義時(shí)不能有任何的參數(shù)和返回值。

雖然一個(gè) package 里面可以寫任意多個(gè) init 函數(shù),但這無論是對于可讀性還是以后的可維護(hù)性來說,我們都強(qiáng)烈建議用戶在一個(gè) package 中每個(gè)文件只寫一個(gè) init 函數(shù)。

Go 程序會(huì)自動(dòng)調(diào)用 init()main() ,所以不需要在任何地方調(diào)用這兩個(gè)函數(shù)。每個(gè) package 中的 init 函數(shù)都是可選的,但 package main 只能包含一個(gè) main 函數(shù)

程序的初始化和執(zhí)行都起始于 main 包。如果 main 包還導(dǎo)入了其它的包,那么就會(huì)在編譯時(shí)將它們依次導(dǎo)入。有時(shí)一個(gè)包會(huì)被多個(gè)包同時(shí)導(dǎo)入,那么它只會(huì)被導(dǎo)入一次(例如很多包可能都會(huì)用到 fmt 包,但它只會(huì)被導(dǎo)入一次,因?yàn)闆]有必要導(dǎo)入多次)。

當(dāng)一個(gè)包被導(dǎo)入時(shí),如果該包還導(dǎo)入了其它的包,那么會(huì)先將其它包導(dǎo)入進(jìn)來,然后再對這些包中的包級(jí)常量和變量進(jìn)行初始化,接著執(zhí)行 init 函數(shù)(如果有的話),依次類推。等所有被導(dǎo)入的包都加載完畢了,就會(huì)開始對 main 包中的包級(jí)常量和變量進(jìn)行初始化,然后執(zhí)行 main 包中的 init 函數(shù)(如果存在的話),最后執(zhí)行 main 函數(shù)。下圖詳細(xì)地解釋了整個(gè)執(zhí)行過程:



見 go 語言基礎(chǔ) 《main函數(shù)和init函數(shù)》 78 頁說明

init 函數(shù)特征總結(jié):

  • 每個(gè)源文件都可以定義一個(gè)或多個(gè)初始化函數(shù),但強(qiáng)烈建議只定義一個(gè)。
  • 編譯器不保證多個(gè)初始化函數(shù)執(zhí)行次序。
  • 初始化函數(shù)在單一線程被用,僅執(zhí)行一次。
  • 初始化函數(shù)在包所有全局變量初始化后執(zhí)行
  • 在所有初始化函數(shù)結(jié)束后才執(zhí)行 main.main。
  • init() 函數(shù)不能被其他函數(shù)調(diào)用

所以簡而言之,你只需要記住這三點(diǎn)就可以了:

  • 依賴包按“深度優(yōu)先”的次序進(jìn)行初始化;
  • 每個(gè)包內(nèi)按以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化;
  • 包內(nèi)的多個(gè) init 函數(shù)按出現(xiàn)次序進(jìn)行自動(dòng)調(diào)用;(待確認(rèn)?)

這里舉出《Go并發(fā)編程實(shí)戰(zhàn)》中的例子,幫助理解上面的包初始化,如下:

package main // 命令源碼文件必須在這里聲明自己屬于main包import ( // 引入了代碼包fmt和runtime"fmt""runtime"
)func init() { // 包初始化函數(shù)fmt.Printf("Map: %v\n", m) // 先格式化再打印// 通過調(diào)用runtime包的代碼獲取當(dāng)前機(jī)器所運(yùn)行的操作系統(tǒng)以及計(jì)算架構(gòu)// 而后通過fmt包的Sprintf方法進(jìn)行字符串格式化并賦值給變量infoinfo = fmt.Sprintf("OS: %s, Arch: %s", runtime.GOOS, runtime.GOARCH)
}// 非局部變量,map類型,且已初始化
var m map[int]string = map[int]string{1: "A", 2: "B", 3: "C"}
var info string // 非局部變量,string類型,未被初始化func main() { // 命令源碼文件必須有的入口函數(shù)fmt.Println(info) // 打印變量info
}

輸出

Map: map[1:A 2:B 3:C]
OS: windows, Arch: amd64

在同一個(gè)代碼包中,可以存在多個(gè)代碼包初始化函數(shù),甚至代碼包內(nèi)的每一個(gè)源碼文件都可以定義多個(gè)代碼包初始化函數(shù)。

Go 語言編譯器不能保證同一個(gè)代碼包中的多個(gè)代碼包初始化函數(shù)的執(zhí)行順序。如果要求按特定順序執(zhí)行的話,可以考慮使用 Channel

8. 編譯速度

當(dāng)我們修改了一個(gè)源文件,我們必須重新編譯該源文件對應(yīng)的包和所有依賴該包的其他包。

即使是從頭構(gòu)建, Go 語言編譯器的編譯速度也明顯快于其它編譯語言。 Go 語言的閃電般的編譯速度主要得益于三個(gè)語言特性。

  1. 所有導(dǎo)入的包必須在每個(gè)文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個(gè)源文件來判斷包的依賴關(guān)系。
  2. 禁止包的環(huán)狀依賴,因?yàn)闆]有循環(huán)依賴,包的依賴關(guān)系形成一個(gè)有向無環(huán)圖,每個(gè)包可以被獨(dú)立編譯,而且很可能是被并發(fā)編譯。
  3. 編譯后包的目標(biāo)文件不僅僅記錄包本身的導(dǎo)出信息,目標(biāo)文件同時(shí)還記錄了包的依賴關(guān)系。因此,在編譯一個(gè)包的時(shí)候,編譯器只需要讀取每個(gè)直接導(dǎo)入包的目標(biāo)文件,而不需要遍歷所有依賴的的文件(譯注:很多都是重復(fù)的間接依賴)。

總結(jié)

以上是生活随笔為你收集整理的Go 学习笔记(3)— 包概念、包特点、包名约束、main 包、包的声明、包的引用、包初始化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。