中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

長(zhǎng)沙建個(gè)網(wǎng)站一般需要多少錢網(wǎng)絡(luò)營(yíng)銷策劃方案

長(zhǎng)沙建個(gè)網(wǎng)站一般需要多少錢,網(wǎng)絡(luò)營(yíng)銷策劃方案,微商好貨源app下載,網(wǎng)站建設(shè)問(wèn)答Go包介紹與初始化:搞清Go程序的執(zhí)行次序 文章目錄 Go包介紹與初始化:搞清Go程序的執(zhí)行次序一、main.main 函數(shù):Go 應(yīng)用的入口函數(shù)1.1 main.main 函數(shù)1.2 main.main 函數(shù)特點(diǎn) 二、包介紹2.1 包介紹與聲明2.2 非 main包的 main 函數(shù)2.3 包的命名…

Go包介紹與初始化:搞清Go程序的執(zhí)行次序

文章目錄

  • Go包介紹與初始化:搞清Go程序的執(zhí)行次序
    • 一、main.main 函數(shù):Go 應(yīng)用的入口函數(shù)
      • 1.1 main.main 函數(shù)
      • 1.2 main.main 函數(shù)特點(diǎn)
    • 二、包介紹
      • 2.1 包介紹與聲明
      • 2.2 非 main包的 main 函數(shù)
      • 2.3 包的命名規(guī)則
    • 三、包的導(dǎo)入
      • 3.1 包的導(dǎo)入介紹
      • 3.2 導(dǎo)入多個(gè)包
      • 3.2 包的別名
    • 四、神器的下劃線
      • 4.1 下劃線的作用
      • 4.2 下劃線在代碼中
      • 4.3 下劃線在import中
    • 五、init 函數(shù):Go 包的初始化函數(shù)
      • 5.1 init 函數(shù) 介紹
      • 5.2 init 函數(shù) 特點(diǎn)
    • 六、Go 包的初始化次序
      • 6.1 包的初始化次序探究
      • 6.2 包的初始化原則
    • 七、init 函數(shù)的常用用途
      • 7.1 用途一:重置包級(jí)變量值
      • 7.2 用途二:實(shí)現(xiàn)對(duì)包級(jí)變量的復(fù)雜初始化
      • 7.3 用途三:在 init 函數(shù)中實(shí)現(xiàn)“注冊(cè)模式”

一、main.main 函數(shù):Go 應(yīng)用的入口函數(shù)

1.1 main.main 函數(shù)

在Go語(yǔ)言中,main函數(shù)是任何Go應(yīng)用的入口函數(shù)–用戶層入口。當(dāng)你運(yùn)行一個(gè)Go程序時(shí),操作系統(tǒng)會(huì)首先調(diào)用main函數(shù),然后程序開(kāi)始執(zhí)行。main 函數(shù)的函數(shù)原型是這樣的:

package mainfunc main() {// 用戶層執(zhí)行邏輯... ...
}

你的程序的執(zhí)行會(huì)從main函數(shù)開(kāi)始,會(huì)在這個(gè)函數(shù)內(nèi)按照它的調(diào)用順序展開(kāi)。

1.2 main.main 函數(shù)特點(diǎn)

main.main函數(shù)是Go應(yīng)用程序的入口函數(shù),它具有一些特點(diǎn)和規(guī)定,使得Go程序的執(zhí)行流程有一定的規(guī)范性。以下是關(guān)于main.main函數(shù)的特點(diǎn):

  1. 唯一入口點(diǎn): 在一個(gè)Go應(yīng)用程序中,只能有一個(gè)main.main函數(shù)。這是整個(gè)程序的唯一入口點(diǎn),程序的執(zhí)行將從這里開(kāi)始。如果存在多個(gè)main函數(shù),編譯時(shí)會(huì)報(bào)錯(cuò)。
  2. 不接受參數(shù): main.main函數(shù)不接受任何參數(shù),它沒(méi)有輸入?yún)?shù),也沒(méi)有返回值。這是Go語(yǔ)言的規(guī)定,而程序的命令行參數(shù)通常通過(guò)os.Args等方式獲取。

二、包介紹

2.1 包介紹與聲明

在Go中,包(Package)是組織和管理代碼的基本單元。包包括一組相關(guān)的函數(shù)、類型和變量,它們可以被導(dǎo)入到其他Go文件中以便重復(fù)使用。Go標(biāo)準(zhǔn)庫(kù)以及第三方庫(kù)都是以包的形式提供的。

每個(gè)Go文件都屬于一個(gè)包,你可以使用package關(guān)鍵字來(lái)指定聲明一個(gè)文件屬于哪個(gè)包。例如:

package main

2.2 非 main包的 main 函數(shù)

除了 main 包外,其他包也可以擁有自己的名為 main 的函數(shù)或方法。但按照 Go 的可見(jiàn)性規(guī)則(小寫字母開(kāi)頭的標(biāo)識(shí)符為非導(dǎo)出標(biāo)識(shí)符),**非 main 包中自定義的 main 函數(shù)僅限于包內(nèi)使用,**就像下面代碼這樣,這是一段在非 main 包中定義 main 函數(shù)的代碼片段:

package pkg1import "fmt"func Main() {main()
}func main() {fmt.Println("main func for pkg1")
}  

你可以看到,這里 main 函數(shù)就主要是用來(lái)在包 pkg1 內(nèi)部使用的,它是沒(méi)法在包外使用的。

2.3 包的命名規(guī)則

  • 在Go語(yǔ)言中,包的名稱通常使用小寫字母,具有簡(jiǎn)潔的、描述性的名稱。這有助于提高代碼的可讀性和可維護(hù)性。標(biāo)準(zhǔn)庫(kù)中的包通常具有非常清晰的包名,例如fmt、math、strings等。
  • 在Go語(yǔ)言中,包級(jí)別的標(biāo)識(shí)符(變量、函數(shù)、類型等)的可見(jiàn)性是由其首字母的大小寫來(lái)決定的。如果一個(gè)標(biāo)識(shí)符以大寫字母開(kāi)頭,它就是可導(dǎo)出的(公有的),可以被其他包訪問(wèn)。如果以小寫字母開(kāi)頭,它就是包內(nèi)私有的,只能在包內(nèi)部使用。

三、包的導(dǎo)入

3.1 包的導(dǎo)入介紹

要在Go程序中使用其他包的功能,你需要導(dǎo)入這些包。使用import關(guān)鍵字來(lái)導(dǎo)入包,導(dǎo)入語(yǔ)句通常放在文件的頂部。一個(gè)典型的包導(dǎo)入語(yǔ)句的格式如下:

import "包的導(dǎo)入路徑"

其中,包的導(dǎo)入路徑是指被導(dǎo)入包的唯一標(biāo)識(shí)符,通常是包的名稱或路徑,它用于告訴Go編譯器去哪里找到這個(gè)包的代碼。

例如,導(dǎo)入標(biāo)準(zhǔn)庫(kù)中的fmt包可以這樣做:

import "fmt"

然后,你就可以在你的程序中使用fmt包提供的函數(shù)和類型。

3.2 導(dǎo)入多個(gè)包

在Go程序中,你可以一次導(dǎo)入多個(gè)包,只需在import語(yǔ)句中列出這些包的導(dǎo)入路徑,用括號(hào)()括起來(lái)并以括號(hào)內(nèi)的方式分隔包的導(dǎo)入路徑。

示例:

import ("fmt""math""net/http"
)

這個(gè)示例中導(dǎo)入了fmt、mathnet/http三個(gè)包。這種方式使你可以更清晰地組織你的導(dǎo)入語(yǔ)句,以便程序更易讀。

注意:Go語(yǔ)言的編譯器會(huì)自動(dòng)檢測(cè)哪些導(dǎo)入的包是真正被使用的,未使用的導(dǎo)入包不會(huì)引起編譯錯(cuò)誤,但通常被視為不良實(shí)踐。在Go中,未使用的導(dǎo)入包可能會(huì)引起代碼不清晰,因此應(yīng)該避免導(dǎo)入不需要的包。

3.2 包的別名

在Go語(yǔ)言中,你可以使用包的別名(package alias)來(lái)為一個(gè)導(dǎo)入的包賦予一個(gè)不同的名稱,以便在代碼中引用它。包的別名通常用于以下情況:

  1. 避免包名沖突:當(dāng)你導(dǎo)入多個(gè)包時(shí),有可能出現(xiàn)包名沖突,此時(shí)你可以為一個(gè)或多個(gè)包使用別名來(lái)解決沖突。
  2. 簡(jiǎn)化包名:有時(shí),包的導(dǎo)入路徑可能很長(zhǎng),為了減少代碼中的冗長(zhǎng),你可以為包使用一個(gè)短的別名。

使用包的別名是非常簡(jiǎn)單的,只需在導(dǎo)入語(yǔ)句中使用as關(guān)鍵字為包指定一個(gè)別名。以下是示例:

import fm "fmt"

在上面的示例中,fmfmt包的別名?,F(xiàn)在,你可以在代碼中使用fm來(lái)代替fmt,例如:

fm.Println("Hello, World!")

這樣,你就可以使用更短的fm來(lái)調(diào)用fmt包的函數(shù),以減少代碼中的冗長(zhǎng)。

包的別名可以根據(jù)需要自定義,但通常建議選擇一個(gè)有意義的別名,以使代碼更易讀。使用別名時(shí)要注意避免產(chǎn)生混淆,要確保別名不與其他標(biāo)識(shí)符(如變量名或函數(shù)名)發(fā)生沖突。

四、神器的下劃線

4.1 下劃線的作用

下劃線 _ 在Go語(yǔ)言中用于以下幾個(gè)不同的場(chǎng)景:

  1. 匿名變量: _ 可以用作匿名變量,用于忽略某個(gè)值。當(dāng)你希望某個(gè)值返回但又不需要使用它時(shí),可以將其賦值給 _。
  2. 空標(biāo)識(shí)符: _ 也被稱為空標(biāo)識(shí)符,它用于聲明但不使用變量或?qū)氚皇褂冒臉?biāo)識(shí)符。這是為了確保代碼通過(guò)編譯,但不會(huì)產(chǎn)生未使用變量或包的警告。

4.2 下劃線在代碼中

在代碼中,下劃線 _ 可以用作匿名變量,用于忽略某個(gè)值。這通常在函數(shù)多返回值中使用,如果你只關(guān)心其中的某些值而不需要其他返回值,可以將其賦值給 _。

示例:

x, _ := someFunction() // 忽略第二個(gè)返回值

在上面的示例中,_ 用于忽略 someFunction 函數(shù)的第二個(gè)返回值。

4.3 下劃線在import中

  • 當(dāng)導(dǎo)入一個(gè)包時(shí),該包下的文件里所有init()函數(shù)都會(huì)被執(zhí)行,然而,有些時(shí)候我們并不需要把整個(gè)包都導(dǎo)入進(jìn)來(lái),僅僅是是希望它執(zhí)行init()函數(shù)而已。
  • 這個(gè)時(shí)候就可以使用 import _ 引用該包。即使用 import _ 包路徑 只是引用該包,僅僅是為了調(diào)用init()函數(shù),所以無(wú)法通過(guò)包名來(lái)調(diào)用包中的其他函數(shù)。

以下是一個(gè)示例,演示如何使用 import _ 引用一個(gè)包以執(zhí)行其 init() 函數(shù):

項(xiàng)目結(jié)構(gòu):

src
|
+--- main.go
|
+--- hello|+--- hello.go

main.go 文件

package mainimport _ "./hello"func main() {// hello.Print() //編譯報(bào)錯(cuò):./main.go:6:5: undefined: hello
}

hello.go 文件

package helloimport "fmt"func init() {fmt.Println("imp-init() come here.")
}func Print() {fmt.Println("Hello!")
}

輸出結(jié)果:

    imp-init() come here.

五、init 函數(shù):Go 包的初始化函數(shù)

5.1 init 函數(shù) 介紹

init 函數(shù)是在Go包的初始化階段自動(dòng)調(diào)用的函數(shù)。它的目的是執(zhí)行一些包級(jí)別的初始化工作,例如設(shè)置變量、初始化數(shù)據(jù)、連接數(shù)據(jù)庫(kù)等。init 函數(shù)沒(méi)有參數(shù),也沒(méi)有返回值,它的定義形式如下:

func init() {// 包初始化邏輯... ...
}

5.2 init 函數(shù) 特點(diǎn)

init 函數(shù)有以下特點(diǎn):

  1. 自動(dòng)執(zhí)行: init 函數(shù)不需要手動(dòng)調(diào)用,它會(huì)在程序啟動(dòng)時(shí)自動(dòng)執(zhí)行。這確保了包的初始化工作在程序開(kāi)始執(zhí)行之前完成。
  2. 包級(jí)別: init 函數(shù)是包級(jí)別的,因此它只能在包的內(nèi)部定義。不同包中的 init 函數(shù)互不影響,它們獨(dú)立執(zhí)行。
  3. 多個(gè) init 函數(shù): 一個(gè)包可以包含多個(gè) init 函數(shù),它們按照定義的順序依次執(zhí)行。被導(dǎo)入的包的 init 函數(shù)會(huì)在導(dǎo)入它的包的 init 函數(shù)之前執(zhí)行。
  4. 沒(méi)有參數(shù)和返回值: 和前面main.main 函數(shù)一樣,init 函數(shù)也是一個(gè)無(wú)參數(shù)無(wú)返回值的函數(shù),它只用于執(zhí)行初始化工作,而不與其他函數(shù)交互。
  5. 順序執(zhí)行: 由于 init 函數(shù)的執(zhí)行順序是根據(jù)包的導(dǎo)入順序確定的,因此在編寫代碼時(shí)應(yīng)該謹(jǐn)慎考慮包的依賴關(guān)系,以確保正確的初始化順序。
  6. 可用于注冊(cè)和初始化: init 函數(shù)通常用于執(zhí)行包的初始化工作,也可用于在導(dǎo)入包時(shí)注冊(cè)一些功能,例如數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序的注冊(cè)。

這里要特別注意的是,在 Go 程序中我們不能手工顯式地調(diào)用 init,否則就會(huì)收到編譯錯(cuò)誤,就像下面這個(gè)示例,它表示的手工顯式調(diào)用 init 函數(shù)的錯(cuò)誤做法:

package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {init()
}

構(gòu)建并運(yùn)行上面這些示例代碼之后,Go 編譯器會(huì)報(bào)下面這個(gè)錯(cuò)誤:

$go run call_init.go 
./call_init.go:10:2: undefined: init

接著,我們將代碼修改如下:

package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {fmt.Println("this is main")
}

Go 編譯器運(yùn)行結(jié)果如下:

init invoked
this is main

我們看到,在初始化 Go 包時(shí),Go 會(huì)按照一定的次序,逐一、順序地調(diào)用這個(gè)包的 init 函數(shù)。一般來(lái)說(shuō),先傳遞給 Go 編譯器的源文件中的 init 函數(shù),會(huì)先被執(zhí)行;而同一個(gè)源文件中的多個(gè) init 函數(shù),會(huì)按聲明順序依次執(zhí)行。所以說(shuō),在Go中,main.main 函數(shù)可能并不是第一個(gè)被執(zhí)行的函數(shù)。

六、Go 包的初始化次序

6.1 包的初始化次序探究

我們從程序邏輯結(jié)構(gòu)角度來(lái)看,Go 包是程序邏輯封裝的基本單元,每個(gè)包都可以理解為是一個(gè)“自治”的、封裝良好的、對(duì)外部暴露有限接口的基本單元。一個(gè) Go 程序就是由一組包組成的,程序的初始化就是這些包的初始化。每個(gè) Go 包還會(huì)有自己的依賴包、常量、變量、init 函數(shù)(其中 main 包有 main 函數(shù))等。

在平時(shí)開(kāi)發(fā)中,我們?cè)陂喿x和理解代碼的時(shí)候,需要知道這些元素在在程序初始化過(guò)程中的初始化順序,這樣便于我們確定在某一行代碼處這些元素的當(dāng)前狀態(tài)。

下面,我們就通過(guò)一張流程圖,來(lái)了解 Go 包的初始化次序:

WechatIMG4036

這里,我們來(lái)看看具體的初始化步驟。

首先,main 包依賴 pkg1pkg4 兩個(gè)包,所以第一步,Go 會(huì)根據(jù)包導(dǎo)入的順序,先去初始化 main 包的第一個(gè)依賴包 pkg1。

第二步,Go 在進(jìn)行包初始化的過(guò)程中,會(huì)采用“深度優(yōu)先”的原則,遞歸初始化各個(gè)包的依賴包。在上圖里,pkg1 包依賴 pkg2 包,pkg2 包依賴 pkg3 包,pkg3 沒(méi)有依賴包,于是 Go 在 pkg3 包中按照“常量 -> 變量 -> init 函數(shù)”的順序先對(duì) pkg3 包進(jìn)行初始化;

緊接著,在 pkg3 包初始化完畢后,Go 會(huì)回到 pkg2 包并對(duì) pkg2 包進(jìn)行初始化,接下來(lái)再回到 pkg1 包并對(duì) pkg1 包進(jìn)行初始化。在調(diào)用完 pkg1 包的 init 函數(shù)后,Go 就完成了 main 包的第一個(gè)依賴包 pkg1 的初始化。

接下來(lái),Go 會(huì)初始化 main 包的第二個(gè)依賴包 pkg4pkg4 包的初始化過(guò)程與 pkg1 包類似,也是先初始化它的依賴包 pkg5,然后再初始化自身;

然后,當(dāng) Go 初始化完 pkg4 包后也就完成了對(duì) main 包所有依賴包的初始化,接下來(lái)初始化 main 包自身。

最后,在 main 包中,Go 同樣會(huì)按照“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化,執(zhí)行完這些初始化工作后才正式進(jìn)入程序的入口函數(shù) main 函數(shù)。

現(xiàn)在,我們可以通過(guò)一段代碼示例來(lái)驗(yàn)證一下 Go 程序啟動(dòng)后,Go 包的初始化次序是否是正確的,示例程序的結(jié)構(gòu)如下:

prog-init-order
├── main.go
├── pkg1
│   └── pkg1.go
├── pkg2
│   └── pkg2.go
└── pkg3└── pkg3.go

這里我只列出了 main 包的代碼,pkg1、pkg2pkg3 可可以到代碼倉(cāng)庫(kù)中查看。

package mainimport ("fmt"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg1"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg2"
)var (_  = constInitCheck()v1 = variableInit("v1")v2 = variableInit("v2")
)const (c1 = "c1"c2 = "c2"
)func constInitCheck() string {if c1 != "" {fmt.Println("main: const c1 has been initialized!")}if c2 != "" {fmt.Println("main: const c2 has been initialized!")}return ""
}func variableInit(name string) string {fmt.Printf("main: var %s has been initialized\n", name)return name
}func init() {fmt.Println("main: first init function invoked")
}func init() {fmt.Println("main: second init function invoked")
}func main() {//
}

我們可以看到,在 main 包中其實(shí)并沒(méi)有使用 pkg1 和 pkg2 中的函數(shù)或方法,而是直接通過(guò)空導(dǎo)入的方式“觸發(fā)”pkg1 包和 pkg2 包的初始化(pkg1 包和和 pkg2 包都通過(guò)空導(dǎo)入的方式依賴 pkg3 包的,),下面是這個(gè)程序的運(yùn)行結(jié)果:

$go run main.go
pkg3: const c has been initialized
pkg3: var v has been initialized
pkg3: init func invoked
pkg1: const c has been initialized
pkg1: var v has been initialized
pkg1: init func invoked
pkg2: const c has been initialized
pkg2: var v has been initialized
pkg2: init func invoked
main: const c1 has been initialized
main: const c2 has been initialized
main: var v1 has been initialized
main: var v2 has been initialized
main: first init func invoked
main: second init func invoked

正如我們預(yù)期的那樣,Go 運(yùn)行時(shí)是按照“pkg3 -> pkg1 -> pkg2 -> main”的順序,來(lái)對(duì) Go 程序的各個(gè)包進(jìn)行初始化的,而在包內(nèi),則是以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化。此外,main 包的兩個(gè) init 函數(shù),會(huì)按照在源文件 main.go 中的出現(xiàn)次序進(jìn)行調(diào)用。根據(jù) Go 語(yǔ)言規(guī)范,**一個(gè)被多個(gè)包依賴的包僅會(huì)初始化一次,**因此這里的 pkg3 包僅會(huì)被初始化了一次。

6.2 包的初始化原則

根據(jù)以上,包的初始化按照依賴關(guān)系的順序執(zhí)行,遵循以下規(guī)則:

  1. 依賴包按照 “深度優(yōu)先” 的方式進(jìn)行初始化,即先初始化最底層的依賴包。
  2. 在每個(gè)包內(nèi)部以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化。
  3. 包內(nèi)的多個(gè) init 函數(shù)按照它們?cè)诖a中的出現(xiàn)順序依次自動(dòng)調(diào)用。

七、init 函數(shù)的常用用途

Go 包初始化時(shí),init 函數(shù)的初始化次序在變量之后,這給了開(kāi)發(fā)人員在 init 函數(shù)中對(duì)包級(jí)變量進(jìn)行進(jìn)一步檢查與操作的機(jī)會(huì)。

7.1 用途一:重置包級(jí)變量值

init 函數(shù)就好比 Go 包真正投入使用之前唯一的“質(zhì)檢員”,負(fù)責(zé)對(duì)包內(nèi)部以及暴露到外部的包級(jí)數(shù)據(jù)(主要是包級(jí)變量)的初始狀態(tài)進(jìn)行檢查。在 Go 標(biāo)準(zhǔn)庫(kù)中,我們能發(fā)現(xiàn)很多 init 函數(shù)被用于檢查包級(jí)變量的初始狀態(tài)的例子,標(biāo)準(zhǔn)庫(kù) flag 包對(duì) init 函數(shù)的使用就是其中的一個(gè),這里我們簡(jiǎn)單來(lái)分析一下。

flag 包定義了一個(gè)導(dǎo)出的包級(jí)變量 CommandLine,如果用戶沒(méi)有通過(guò) flag.NewFlagSet 創(chuàng)建新的代表命令行標(biāo)志集合的實(shí)例,那么 CommandLine 就會(huì)作為 flag 包各種導(dǎo)出函數(shù)背后,默認(rèn)的代表命令行標(biāo)志集合的實(shí)例。

而在 flag 包初始化的時(shí)候,由于 init 函數(shù)初始化次序在包級(jí)變量之后,因此包級(jí)變量 CommandLine 會(huì)在 init 函數(shù)之前被初始化了,可以看如下代碼:

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {f := &FlagSet{name:          name,errorHandling: errorHandling,}f.Usage = f.defaultUsagereturn f
}func (f *FlagSet) defaultUsage() {if f.name == "" {fmt.Fprintf(f.Output(), "Usage:\n")} else {fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)}f.PrintDefaults()
}

我們可以看到,在通過(guò) NewFlagSet 創(chuàng)建 CommandLine 變量綁定的 FlagSet 類型實(shí)例時(shí),CommandLineUsage 字段被賦值為 defaultUsage。

也就是說(shuō),如果保持現(xiàn)狀,那么使用 flag 包默認(rèn) CommandLine 的用戶就無(wú)法自定義 usage 的輸出了。于是,flag 包在 init 函數(shù)中重置了 CommandLineUsage 字段:

func init() {CommandLine.Usage = commandLineUsage // 重置CommandLine的Usage字段
}func commandLineUsage() {Usage()
}var Usage = func() {fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])PrintDefaults()
}

這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn),CommandLineUsage 字段,設(shè)置為了一個(gè) flag 包內(nèi)的未導(dǎo)出函數(shù) commandLineUsage,后者則直接使用了 flag 包的另外一個(gè)導(dǎo)出包變量 Usage。這樣,就可以通過(guò) init 函數(shù),將 CommandLine 與包變量 Usage 關(guān)聯(lián)在一起了。

然后,當(dāng)用戶將自定義的 usage 賦值給了 flag.Usage 后,就相當(dāng)于改變了默認(rèn)代表命令行標(biāo)志集合的 CommandLine 變量的 Usage。這樣當(dāng) flag 包完成初始化后,CommandLine 變量便處于一個(gè)合理可用的狀態(tài)了。

7.2 用途二:實(shí)現(xiàn)對(duì)包級(jí)變量的復(fù)雜初始化

有些包級(jí)變量需要一個(gè)比較復(fù)雜的初始化過(guò)程。有些時(shí)候,使用它的類型零值(每個(gè) Go 類型都具有一個(gè)零值定義)或通過(guò)簡(jiǎn)單初始化表達(dá)式不能滿足業(yè)務(wù)邏輯要求,而 init 函數(shù)則非常適合完成此項(xiàng)工作。標(biāo)準(zhǔn)庫(kù) http 包中就有這樣一個(gè)典型示例:

var (http2VerboseLogs    bool // 初始化時(shí)默認(rèn)值為falsehttp2logFrameWrites bool // 初始化時(shí)默認(rèn)值為falsehttp2logFrameReads  bool // 初始化時(shí)默認(rèn)值為falsehttp2inTests        bool // 初始化時(shí)默認(rèn)值為false
)func init() {e := os.Getenv("GODEBUG")if strings.Contains(e, "http2debug=1") {http2VerboseLogs = true // 在init中對(duì)http2VerboseLogs的值進(jìn)行重置}if strings.Contains(e, "http2debug=2") {http2VerboseLogs = true // 在init中對(duì)http2VerboseLogs的值進(jìn)行重置http2logFrameWrites = true // 在init中對(duì)http2logFrameWrites的值進(jìn)行重置http2logFrameReads = true // 在init中對(duì)http2logFrameReads的值進(jìn)行重置}
}

我們可以看到,標(biāo)準(zhǔn)庫(kù) http 包定義了一系列布爾類型的特性開(kāi)關(guān)變量,它們默認(rèn)處于關(guān)閉狀態(tài)(即值為 false),但我們可以通過(guò) GODEBUG 環(huán)境變量的值,開(kāi)啟相關(guān)特性開(kāi)關(guān)。

可是這樣一來(lái),簡(jiǎn)單地將這些變量初始化為類型零值,就不能滿足要求了,所以 http 包在 init 函數(shù)中,就根據(jù)環(huán)境變量 GODEBUG 的值,對(duì)這些包級(jí)開(kāi)關(guān)變量進(jìn)行了復(fù)雜的初始化,從而保證了這些開(kāi)關(guān)變量在 http 包完成初始化后,可以處于合理狀態(tài)。

7.3 用途三:在 init 函數(shù)中實(shí)現(xiàn)“注冊(cè)模式”

首先我們來(lái)看一段使用 lib/pq 包訪問(wèn) PostgreSQL 數(shù)據(jù)庫(kù)的代碼示例:

import ("database/sql"_ "github.com/lib/pq"
)func main() {db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")if err != nil {log.Fatal(err)}age := 21rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)...
}

其實(shí),這是一段“神奇”的代碼。你可以看到示例代碼是以空導(dǎo)入的方式導(dǎo)入 lib/pq 包的,main 函數(shù)中沒(méi)有使用 pq 包的任何變量、函數(shù)或方法,這樣就實(shí)現(xiàn)了對(duì) PostgreSQL 數(shù)據(jù)庫(kù)的訪問(wèn)。而這一切的奧秘,全在 pq 包的 init 函數(shù)中:

func init() {sql.Register("postgres", &Driver{})
}

這個(gè)奧秘就在,我們其實(shí)是利用了用空導(dǎo)入的方式導(dǎo)入 lib/pq 包時(shí)產(chǎn)生的一個(gè)“副作用”,也就是 lib/pq 包作為 main 包的依賴包,它的 init 函數(shù)會(huì)在 pq 包初始化的時(shí)候得以執(zhí)行。

從上面的代碼中,我們可以看到在 pq 包的 init 函數(shù)中,pq 包將自己實(shí)現(xiàn)的 SQL 驅(qū)動(dòng)注冊(cè)到了 database/sql 包中。這樣只要應(yīng)用層代碼在 Open 數(shù)據(jù)庫(kù)的時(shí)候,傳入驅(qū)動(dòng)的名字(這里是“postgres”),那么通過(guò) sql.Open 函數(shù),返回的數(shù)據(jù)庫(kù)實(shí)例句柄對(duì)數(shù)據(jù)庫(kù)進(jìn)行的操作,實(shí)際上調(diào)用的都是 pq 包中相應(yīng)的驅(qū)動(dòng)實(shí)現(xiàn)。

實(shí)際上,這種通過(guò)在 init 函數(shù)中注冊(cè)自己的實(shí)現(xiàn)的模式,就有效降低了 Go 包對(duì)外的直接暴露,尤其是包級(jí)變量的暴露,從而避免了外部通過(guò)包級(jí)變量對(duì)包狀態(tài)的改動(dòng)。

另外,從標(biāo)準(zhǔn)庫(kù) database/sql 包的角度來(lái)看,這種“注冊(cè)模式”實(shí)質(zhì)是一種工廠設(shè)計(jì)模式的實(shí)現(xiàn),sql.Open 函數(shù)就是這個(gè)模式中的工廠方法,它根據(jù)外部傳入的驅(qū)動(dòng)名稱“生產(chǎn)”出不同類別的數(shù)據(jù)庫(kù)實(shí)例句柄。

這種“注冊(cè)模式”在標(biāo)準(zhǔn)庫(kù)的其他包中也有廣泛應(yīng)用,比如說(shuō),使用標(biāo)準(zhǔn)庫(kù) image 包獲取各種格式圖片的寬和高:

package mainimport ("fmt""image"_ "image/gif" // 以空導(dǎo)入方式注入gif圖片格式驅(qū)動(dòng)_ "image/jpeg" // 以空導(dǎo)入方式注入jpeg圖片格式驅(qū)動(dòng)_ "image/png" // 以空導(dǎo)入方式注入png圖片格式驅(qū)動(dòng)"os"
)func main() {// 支持png, jpeg, gifwidth, height, err := imageSize(os.Args[1]) // 獲取傳入的圖片文件的寬與高if err != nil {fmt.Println("get image size error:", err)return}fmt.Printf("image size: [%d, %d]\n", width, height)
}func imageSize(imageFile string) (int, int, error) {f, _ := os.Open(imageFile) // 打開(kāi)圖文文件defer f.Close()img, _, err := image.Decode(f) // 對(duì)文件進(jìn)行解碼,得到圖片實(shí)例if err != nil {return 0, 0, err}b := img.Bounds() // 返回圖片區(qū)域return b.Max.X, b.Max.Y, nil
}

你可以看到,上面這個(gè)示例程序支持 png、jpeg、gif 三種格式的圖片,而達(dá)成這一目標(biāo)的原因,正是 image/png、image/jpegimage/gif 包都在各自的 init 函數(shù)中,將自己“注冊(cè)”到 image 的支持格式列表中了,你可以看看下面這個(gè)代碼:

// $GOROOT/src/image/png/reader.go
func init() {image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}// $GOROOT/src/image/jpeg/reader.go
func init() {image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}// $GOROOT/src/image/gif/reader.go
func init() {image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}  

那么,現(xiàn)在我們了解了 init 函數(shù)的常見(jiàn)用途。init 函數(shù)之所以可以勝任這些工作,恰恰是因?yàn)樗?Go 應(yīng)用初始化次序中的特殊“位次”,也就是 main 函數(shù)之前,常量和變量初始化之后。

http://www.risenshineclean.com/news/29090.html

相關(guān)文章:

  • 金華電子商務(wù)網(wǎng)站建設(shè)百度怎么做廣告推廣
  • 高端型網(wǎng)站制作永久免費(fèi)google搜索引擎
  • 網(wǎng)站建設(shè) 安慶關(guān)鍵詞全網(wǎng)搜索工具
  • 利為匯wordpress教程廈門關(guān)鍵詞seo排名網(wǎng)站
  • 寧城網(wǎng)站建設(shè)公司百度怎么免費(fèi)推廣
  • 游戲網(wǎng)站開(kāi)發(fā)視頻制作一個(gè)簡(jiǎn)單的網(wǎng)站
  • 公司網(wǎng)站沒(méi)備案最近營(yíng)銷熱點(diǎn)
  • 東莞php網(wǎng)站建設(shè)快速排名seo
  • wordpress 整站帶數(shù)據(jù)互動(dòng)營(yíng)銷的方式有哪些
  • asp做微網(wǎng)站設(shè)計(jì)濟(jì)南網(wǎng)站seo公司
  • 自己做的網(wǎng)站標(biāo)題青島網(wǎng)絡(luò)推廣公司排名
  • 企業(yè)負(fù)責(zé)人電話名錄百度搜索優(yōu)化怎么做
  • 企業(yè)做網(wǎng)站有什么好處壞處百度愛(ài)采購(gòu)優(yōu)化軟件
  • 傳媒公司做網(wǎng)站條件如何讓關(guān)鍵詞排名靠前
  • wordpress數(shù)據(jù)表開(kāi)頭小紅書(shū)seo排名規(guī)則
  • wordpress換域名換服務(wù)器寧波seo在線優(yōu)化
  • 長(zhǎng)沙網(wǎng)站搭建百度引流推廣費(fèi)用多少
  • 網(wǎng)站內(nèi)容建設(shè)ppt目前最新的營(yíng)銷方式有哪些
  • 東昌府網(wǎng)站建設(shè)公司營(yíng)銷對(duì)企業(yè)的重要性
  • 網(wǎng)站開(kāi)發(fā)必用代碼西安百度競(jìng)價(jià)托管代運(yùn)營(yíng)
  • 萊蕪 做網(wǎng)站 公司百度權(quán)重提升
  • 淮南網(wǎng)站建設(shè)好的公司百姓網(wǎng)推廣怎么收費(fèi)標(biāo)準(zhǔn)
  • 微網(wǎng)站入口sem和seo的關(guān)系
  • 旅游網(wǎng)站設(shè)計(jì)說(shuō)明書(shū)網(wǎng)絡(luò)營(yíng)銷推廣方案步驟
  • 做百度翻譯英文網(wǎng)站信息流廣告優(yōu)秀案例
  • 法治政府建設(shè)網(wǎng)站四川seo平臺(tái)
  • 大連做網(wǎng)站建設(shè)電腦編程培訓(xùn)學(xué)校哪家好
  • 濟(jì)南網(wǎng)站建設(shè)報(bào)價(jià)收錄網(wǎng)站排名
  • 新網(wǎng)站 不穩(wěn)定惠州seo代理商
  • 網(wǎng)站系統(tǒng)php源碼河南鄭州網(wǎng)站推廣優(yōu)化