Go語言中的init函數: 特點、用途和註意事項

来源:https://www.cnblogs.com/chenjiazhan/archive/2023/06/11/17473207.html
-Advertisement-
Play Games

博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...


1. 引言

在Go語言中,init()函數是一種特殊的函數,用於在程式啟動時自動執行一次。它的存在為我們提供了一種機制,可以在程式啟動時進行一些必要的初始化操作,為程式的正常運行做好準備。

在這篇文章中,我們將詳細探討init()函數的特點、用途和註意事項,希望能幫助你更好地理解和使用這個重要的Go語言特性。

2. init 函數的特點

2.1 自動執行

init()函數的一個重要特點,便是其無需手動調用,它會在程式啟動時自動執行。當程式開始運行時,Go運行時系統會自動調用每個包中的init()函數。下麵是一個示例代碼,演示了init()函數在程式啟動時自動執行的特點:

package main

import "fmt"

func init() {
    fmt.Println("Init function executed")
}

func main() {
    fmt.Println("Main function executed")
}

在這個示例代碼中,我們定義了一個init()函數和一個main()函數。init()函數會在程式啟動時自動執行,而main()函數則是程式的入口函數,會在init()函數執行完畢後執行。

當我們運行這段代碼時,輸出結果如下:

Init function executed
Main function executed

可以看到,init()函數在程式啟動時自動執行,並且在main()函數之前被調用。這證明瞭init()函數在程式啟動時會自動執行,可以用於在程式啟動前進行一些必要的初始化操作。

2.2 在包級別變數初始化後執行

當一個包被引入或使用時,其中會先初始化包級別常量和變數。然後,按照init()函數在代碼中的聲明順序,其會被自動執行。下麵是一個簡單代碼的說明:

package main

import "fmt"

var (
        Var1 = "Variable 1"
        Var2 = "Variable 2"
)

func init() {
        fmt.Println("Init function executed")
        fmt.Println("Var1:", Var1)
        fmt.Println("Var2:", Var2)
}

func main() {
        fmt.Println("Main function executed")
}

在這個示例代碼中,我們聲明瞭包級別的常量,併在init()函數中列印它們的值。在main()函數中,我們列印了一條信息。當我們運行這段代碼時,輸出結果如下:

Init function executed
Var1: Variable 1
Var2: Variable 2
Main function executed

可以看到,init()函數在包的初始化階段被自動執行,並且在包級別常量和變數被初始化之後執行。這驗證了init()函數的執行順序。因為包級別常量和變數的初始化是在init()函數執行之前進行的。因此,在init()函數中可以安全地使用這些常量和變數。

2.3 執行順序不確定

在一個包中,如果存在多個init()函數,它們的執行順序是按照在代碼中出現的順序確定的。先出現的init()函數會先執行,後出現的init()函數會後執行。

具體來說,按照代碼中的順序定義了init()函數的先後順序。如果在同一個源文件中定義了多個init()函數,它們的順序將按照在源代碼中的出現順序來執行。下麵通過一個示例代碼來說明:

package main

import "fmt"

func init() {
        fmt.Println("First init function")
}

func init() {
        fmt.Println("Second init function")
}

func main() {
        fmt.Println("Main function executed")
}

在這個示例中,我們在同一個包中定義了兩個init()函數。它們按照在源代碼中的出現順序進行執行。當我們運行這段代碼時,輸出結果為:

First init function
Second init function
Main function executed

可以看到,先出現的init()函數先執行,後出現的init()函數後執行。

但是重點在於,如果多個init()函數分別位於不同的源文件中,它們之間的執行順序是不確定的。這是因為編譯器在編譯時可能會以不同的順序處理這些源文件,從而導致init()函數的執行順序不確定。

總結起來,同一個源文件中定義的多個init()函數會按照在代碼中的出現順序執行,但多個源文件中的init()函數執行順序是不確定的。

3. init 函數的用途

3.1 初始化全局變數

在大多數情況下,我們可以直接在定義全局變數或常量時賦初值,而不需要使用 init() 函數來進行初始化。直接在定義時賦值的方式更為簡潔和直觀。

然而,有時候我們可能需要更複雜的邏輯來初始化全局變數或常量。這些邏輯可能需要運行時計算、讀取配置文件、進行網路請求等操作,無法在定義時直接賦值。在這種情況下,我們可以使用 init() 函數來實現這些複雜的初始化邏輯。

讓我們通過一個示例來說明這種情況。假設我們有一個全局變數 Config 用於存儲應用程式的配置信息,我們希望在程式啟動時從配置文件中讀取配置併進行初始化。這時就可以使用 init() 函數來實現:

package main

import (
        "fmt"
        "os"
)

var Config map[string]string

func init() {
        Config = loadConfig()
}

func loadConfig() map[string]string {
        // 從配置文件中讀取配置信息的邏輯
        // 這裡只是一個示例,實際中可能涉及更複雜的操作
        config := make(map[string]string)
        config["key1"] = "value1"
        config["key2"] = "value2"
        return config
}

func main() {
        fmt.Println("Config:", Config)
        // 其他業務邏輯...
}

在這個示例中,我們定義了一個全局變數 Config,併在 init() 函數中調用 loadConfig() 函數來讀取配置文件併進行初始化。在 loadConfig() 函數中,我們模擬了從配置文件中讀取配置信息的邏輯,並返回一個配置的 map

當程式啟動時,init() 函數會被自動調用,執行初始化邏輯並將讀取到的配置信息賦值給全局變數 Config。這樣,在應用程式的其他地方可以直接使用 Config 來獲取配置信息。

使用 init() 函數來初始化全局變數或常量的好處是,可以在包初始化階段確保它們被正確初始化,並且可以執行一些複雜的邏輯,例如從文件中讀取配置、初始化資料庫連接等。

3.2 執行一些必要的驗證操作

init() 函數也通常用於執行一些檢查操作,以確保程式在運行之前滿足特定的條件或要求。這些檢查操作的目的是確保程式在正式運行之前滿足特定的條件,從而避免出現潛在的問題或錯誤。下麵是一個簡單的示例,說明瞭使用 init() 函數執行檢查操作的必要性:

package main

import (
        "fmt"
        "os"
)

var config *Config

func init() {
        err := loadConfig()
        if err != nil {
                fmt.Println("Failed to load configuration:", err)
                os.Exit(1)
        }

        err = validateConfig()
        if err != nil {
                fmt.Println("Invalid configuration:", err)
                os.Exit(1)
        }
}

func loadConfig() error {
        // 從配置文件或環境變數中載入配置信息,並初始化 config 對象
        // ...
        return nil
}

func validateConfig() error {
        // 驗證配置是否滿足特定的要求或約束
        // ...
        return nil
}

func main() {
        // 在這裡可以進行其他操作,前提是配置已經載入並且是有效的
        // ...
}

在這個示例中,我們假設程式需要載入配置信息,並對配置進行驗證。在 init() 函數中,我們通過調用 loadConfig() 函數載入配置信息,並調用 validateConfig() 函數對配置進行驗證。

如果配置載入或驗證過程中出現錯誤,我們可以輸出錯誤信息,並使用 os.Exit() 函數終止程式的運行。這樣可以避免在不滿足條件或不正確的配置下運行程式,從而減少可能的問題或錯誤。

通過使用 init() 函數執行檢查操作可以確保程式在正式運行之前滿足特定的條件,並提前處理錯誤情況,從而增加程式的可靠性和可維護性。這樣可以減少在運行時出現問題的可能性,並提高代碼的可讀性和可維護性。

4. init 函數的註意事項

4.1 init 函數不能被顯式調用

當我們定義一個 init() 函數時,它會在程式啟動時自動執行,而無法被顯式調用。下麵通過一個示例代碼來簡單說明:

package main

import "fmt"

func init() {
        fmt.Println("This is the init() function.")
}

func main() {
        fmt.Println("This is the main() function.")

        // 無法顯式調用 init() 函數
        // init() // 這行代碼會導致編譯錯誤
}

在這個示例中,我們定義了一個 init() 函數,併在其中列印一條消息。然後,在 main() 函數中列印另一條消息。在 main() 函數中,我們嘗試顯式調用 init() 函數,但是會導致編譯錯誤。這是因為 init() 函數是在程式啟動時自動調用的,無法在代碼中進行顯式調用。

如果我們嘗試去調用 init() 函數,編譯器會報錯,提示 undefined: init,因為它不是一個可調用的函數。它的執行是由編譯器在程式啟動時自動觸發的,無法通過函數調用來控制。

4.2 init 函數只執行一次

init() 函數在應用程式運行期間只會執行一次。它在程式啟動時被調用,並且僅被調用一次。當一個包被導入時,其中定義的 init() 函數會被自動執行。

同時,即使同一個包被導入了多次,其中的 init() 函數也只會被執行一次。這是因為 Go 編譯器和運行時系統會確保在整個應用程式中只執行一次每個包的 init() 函數。下麵通過一個代碼來進行說明:

首先,我們創建一個名為util的包,其中包含一個全局變數counter和一個init()函數,它會將counter的值增加1。

// util.go
package util

import "fmt"

var counter int

func init() {
        counter++
        fmt.Println("init() function in util package executed. Counter:", counter)
}

func GetCounter() int {
        return counter
}

接下來,我們創建兩個獨立的包,分別為package1package2。這兩個包都會同時導入util包。

// package1.go
package package1

import (
        "fmt"
        "util"
)

func init() {
        fmt.Println("init() function in package1 executed. Counter:", util.GetCounter())
}
// package2.go
package package2

import (
        "fmt"
        "util"
)

func init() {
        fmt.Println("init() function in package2 executed. Counter:", util.GetCounter())
}

最後,我們創建一個名為main.go的程式,導入package1package2

// main.go
package main

import (
        "fmt"
        "package1"
        "package2"
)

func main() {
        fmt.Println("Main function")
}

運行上述程式,我們可以得到以下輸出:

init() function in util package executed. Counter: 1
init() function in package1 executed. Counter: 1
init() function in package2 executed. Counter: 1
Main function

從輸出可以看出,util包中的init()函數只會執行一次,並且在package1package2init()函數中都能獲取到相同的計數器值。這表明,當多個包同時導入另一個包時,該包中的init()函數只會被執行一次。

4.3 避免在 init 函數中執行耗時操作

當在 init() 函數中執行耗時操作時,會影響應用程式的啟動時間。這是因為 init() 函數在程式啟動時自動調用,而且在其他代碼執行之前執行。如果在 init() 函數中執行耗時操作,會導致應用程式啟動變慢。下麵是一個例子來說明這一點:

package main

import (
        "fmt"
        "time"
)

func init() {
        fmt.Println("Executing init() function...")
        time.Sleep(3 * time.Second) // 模擬耗時操作,睡眠 3 秒鐘
        fmt.Println("Init() function execution completed.")
}

func main() {
        fmt.Println("Executing main() function...")
}

在這個例子中,我們在 init() 函數中使用 time.Sleep() 函數模擬了一個耗時操作,睡眠了 3 秒鐘。然後,在 main() 函數中輸出一條消息。當我們運行這個程式時,會發現在啟動時會有 3 秒鐘的延遲,因為 init() 函數中的耗時操作會在程式啟動時執行,而 main() 函數會在 init() 函數執行完成後才開始執行。

通過這個例子,我們可以看到在 init() 函數中執行耗時操作會影響應用程式的啟動時間。如果有必要執行耗時操作,最好將其移至 main() 函數或其他合適的地方,在應用程式啟動後再執行,以避免啟動階段的延遲。

總之,為了保持應用程式的啟動性能,應避免在 init() 函數中執行耗時操作,儘量將其放在需要時再執行,以避免不必要的啟動延遲。

5. 總結

本文介紹了Go語言中的init()函數的特點,用途和註意事項。

在文章中,我們首先講述了init()函數的特點,包含init函數的自動執行,以及其執行時機的內容,接著詳細講解了init()函數的幾個常見用途,包括初始化全局變數以及執行一些必要的校驗操作。接著我們提到了init()函數的一些註意事項,如init函數不能被顯式調用等。

基於以上內容,完成了對init()函數的介紹,希望能幫助你更好地理解和使用這個重要的Go語言特性。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • List 介面是 Collection 介面的子介面。List 中元素有序,是按照元素的插入順序進行排序的。每個元素都有一個與之關聯的整數型索引(索引從 0 開始),可以根據索引來訪問和操作元素,可以使用普通 for 迴圈遍歷。List 中可以包含重覆的元素。 ...
  • # Go 實現 MySQL 資料庫事務 ## 一、MySQL事務 MySQL事務是指一組資料庫操作,它們被視為一個邏輯單元,並且要麼全部成功執行,要麼全部回滾(撤銷)。事務是資料庫管理系統提供的一種機制,用於確保數據的一致性和完整性。 事務具有以下特性(通常由ACID原則定義): 1. 原子性(At ...
  • 前言 本文主要介紹使用spring boot 配置多個資料庫,即動態資料庫 開始搭建 首先創建一個SpringWeb項目——dynamicdb(spring-boot2.5.7) 然後引入相關依賴lombok、swagger2、mybatis-plus,如下: <?xml version="1.0" ...
  • # todo 列表 - [ ] clang-format - [ ] c++ 整合 # 軟體安裝 略 # 基本的環境搭建 ## 最基本的 vscode 插件 只需要安裝如下兩個插件即可 c/c++ 擴展是為了最基本的代碼提示和調試支持 cmake language support 是為了提示 CMa ...
  • [系列文章目錄和關於我](https://www.cnblogs.com/cuzzz/p/16609728.html) ## 零丶背景 最近有很多想學的,像netty的使用、原理源碼,但是苦於自己對於操作系統和nio瞭解不多,有點無從下手,遂學習之。 ## 一丶網路io的過程 ![image-202 ...
  • 如果某個派生自 QObject 的類重寫 eventFilter 方法,那它就成了事件過濾器(Event Filter)。該方法的聲明如下: virtual bool eventFilter(QObject *watched, QEvent *event); watched 參數是監聽事件的對象,即 ...
  • # Go 連接 MySQL之 MySQL 預處理 ## 一、ChatGPT 關於 MySQL 預處理 的回答 ### 問:什麼是MySQL 的預處理 具體執行過程時什麼 #### ChatGPT 答: MySQL的預處理是一種在執行SQL語句之前,先進行編譯和優化的機制。它將SQL語句分成兩個階段: ...
  • ## UDP 簡介 UDP(User Datagram Protocol,用戶數據報協議)是傳輸層的另一種協議,比 TCP 具有更快的傳輸速度,但是不可靠。UDP 發送的數據單元被稱為 UDP 數據報,當網路傳輸 UDP 數據報時,無法保證數據報一定到達目的地,也無法保證各個數據報按發送的順序到達目 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...