Go語言中的閉包詳解

閉包在Go語言中是一個能夠訪問并操作其外部作用域變量的函數,即使外部函數已經執行完畢。閉包由函數體和其引用的環境(外部變量)組成,及:閉包 = 函數 + 環境。

閉包的特性:

  1. 捕獲外部變量:內部函數可訪問外部作用域的變量
  2. 狀態保持:捕獲的變量生命周期延長
  3. 隔離性:每次調用外部函數會創建新的閉包實例

示例說明

示例1:

package mainimport ("fmt"
)func Exp(n int) func() int {e := 1 // 閉包環境變量,在多次調用中保持狀態return func() int {temp := e    // 1. 保存當前值(閉包捕獲時的值)e *= n       // 2. 更新環境變量(n來自外部函數參數)return temp  // 3. 返回保存的舊值}
}func main() {grow := Exp(2) // 創建閉包實例,此時:// - n 被固定為2// - e 初始化為1,與閉包綁定// 每次調用grow()時的狀態變化:// 調用1: temp=1 → e=2 → return 1 (2^0)// 調用2: temp=2 → e=4 → return 2 (2^1)// 調用3: temp=4 → e=8 → return 4 (2^2)// ...for i := range 10 {fmt.Printf("2^%d=%d\n", i, grow())}
}

輸出:

2^0=1
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128
2^8=256
2^9=512

內存狀態演變示意圖:

調用次數 | e值  | 返回值 | 對應指數
---------------------------------0    | 1    |   -    | 初始狀態1    | 2    |   1    | 2^02    | 4    |   2    | 2^1 3    | 8    |   4    | 2^2...10   | 1024 | 512    | 2^9

?Exp函數的返回值是一個函數,這里將稱成為grow函數,每將它調用一次,變量e就會以指數級增長一次。grow函數引用了Exp函數的兩個變量:en,它們誕生在Exp函數的作用域內,在正常情況下隨著Exp函數的調用結束,這些變量的內存會隨著出棧而被回收。但是由于grow函數引用了它們,所以它們無法被回收,而是逃逸到了堆上,即使Exp函數的生命周期已經結束了,但變量en的生命周期并沒有結束,在grow函數內還能直接修改這兩個變量,grow函數就是一個閉包函數。

示例2:帶參數的閉包(函數工廠)

func multiplier(factor int) func(int) int {return func(x int) int {return x * factor // 捕獲外部參數}
}func main() {double := multiplier(2)triple := multiplier(3)fmt.Println(double(5))  // 10 (2*5)fmt.Println(triple(5))  // 15 (3*5)// 修改外部變量factor := 4specialMulti := func(x int) int { return x * factor }factor = 5 // 閉包使用最新的值fmt.Println(specialMulti(10)) // 50 (5*10)
}

具體執行流程:

  1. multiplier(2)?返回閉包時,捕獲了 factor=2
  2. 當調用?double(5)?時:
    • 閉包接收參數 x=2
    • 執行計算 5 * 2(factor的值)
    • 返回結果10

后面的修改外部變量關鍵作用點:

  1. 延遲綁定:閉包在調用時(而非定義時)獲取變量的當前值
  2. 動態更新:展示閉包捕獲的變量可以響應外部修改
  3. 引用捕獲:驗證Go的閉包捕獲的是變量引用,而非值拷貝

內存狀態變化示意圖:

初始狀態:
factor = 4
specialMulti閉包 → 指向factor的內存地址修改后:
factor = 5
specialMulti閉包 → 仍然指向同一個內存地址調用時計算:
10 * 5 = 50

示例3:狀態隔離(并發安全)

func main() {var wg sync.WaitGroup  // 1. 創建等待組for i := 0; i < 3; i++ {wg.Add(1)  // 2. 每次循環增加計數器go func(id int) {  // 3. 啟動goroutinedefer wg.Done()  // 5. 任務完成時減少計數器fmt.Printf("Goroutine %d\n", id)}(i)  // 4. 傳遞當前i的副本}wg.Wait()  // 6. 阻塞直到計數器歸零
}

輸出(可能順序不同):

Goroutine 0
Goroutine 1
Goroutine 2

關鍵機制說明

1.WaitGroup 三部曲

  • Add(1):在啟動每個goroutine前增加計數器
  • Done():在每個goroutine完成時減少計數器(通過defer確保執行)
  • Wait():主goroutine阻塞等待所有任務完成

2.閉包參數傳遞

go func(id int) { ... }(i)  // 傳遞i的當前值副本

?3.并發執行流程

主goroutine        Goroutine 0      Goroutine 1      Goroutine 2
│─啟動循環─────────┐
│ wg.Add(1)       ├─→ 執行任務
│ 啟動goroutine 0─┤
│ wg.Add(1)       ├─────────────→ 執行任務
│ 啟動goroutine 1─┤
│ wg.Add(1)       ├───────────────────────────→ 執行任務
│ 啟動goroutine 2─┤
│ wg.Wait()───────┼─────────────────────────────────────┤
│                 │←───────────────────── Done() ───────┘

常見陷阱與解決方案

陷阱:循環變量捕獲

func main() {var funcs []func()for i := 0; i < 3; i++ {funcs = append(funcs, func() {fmt.Println(i) // 總是輸出3!})}for _, f := range funcs {f()}
}

輸出:

3
3
3

原因: 所有閉包共享同一個i的引用

解決方案1:參數傳遞

for i := 0; i < 3; i++ {j := i // 創建新變量funcs = append(funcs, func() {fmt.Println(j) // 正確輸出0,1,2})
}

解決方案2:立即執行

for i := 0; i < 3; i++ {func(i int) { // 參數隔離funcs = append(funcs, func() {fmt.Println(i)})}(i)
}

閉包的實際應用場景

  1. 狀態封裝:私有計數器/計時器
  2. 中間件:Web請求處理鏈
       func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {log.Println(r.URL.Path)next.ServeHTTP(w, r)})}
  3. 資源管理:文件操作自動關閉
       func readFile(name string) func() ([]byte, error) {f, err := os.Open(name)return func() ([]byte, error) {defer f.Close() // 捕獲文件句柄return io.ReadAll(f)}}
  4. 函數柯里化:參數分解
       func add(a int) func(int) int {return func(b int) int {return a + b}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/94477.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/94477.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/94477.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【DL學習筆記】Dataset類功能以及自定義

文章目錄一、Dataset 與 DataLoader 功能介紹抽象類Dataset的作用DataLoader 作用兩者關系二、自定義Dataset類Dataset的三個重要方法__len__()方法_getitem__()方法__init__ 方法三、現成的torchvision.datasets模塊MNIST舉例COCODetection舉例torchvision.datasets.MNIST使用…

Python爬蟲實戰:研究python_reference庫,構建技術研究數據系統

1. 引言 1.1 研究背景與意義 在大數據時代,數據已成為重要的生產要素。互聯網作為全球最大的信息庫,蘊含著海量有價值的數據。如何從紛繁復雜的網絡信息中快速、準確地提取所需數據,成為各行各業面臨的重要課題。網絡爬蟲技術作為數據獲取的關鍵手段,能夠模擬人類瀏覽網頁…

Web開發系列-第15章 項目部署-Docker

第15章 項目部署-Docker Docker技術能夠避免部署對服務器環境的依賴&#xff0c;減少復雜的部署流程。 輕松部署各種常見軟件、Java項目 參考文檔&#xff1a;?&#xfeff;??&#xfeff;??????&#xfeff;??&#xfeff;????????第十五章&#xff1a;…

微軟無界鼠標(Mouse without Borders)安裝及使用:多臺電腦共用鼠標鍵盤

文章目錄一、寫在前面二、下載安裝1、兩臺電腦都下載安裝2、被控端3、控制端主機三、使用一、寫在前面 在辦公中&#xff0c;我們經常會遇到這種場景&#xff0c;自己帶著筆記本電腦外加公司配置的臺式機。由于兩臺電腦&#xff0c;所以就需要搭配兩套鍵盤鼠標。對于有限的辦公…

nodejs 編程基礎01-NPM包管理

1:npm 包管理介紹 npm 是nodejs 的包管理工具&#xff0c;類似于java 的maven 和 gradle 等&#xff0c;用來解決nodejs 的依賴包問題 使用場景&#xff1a;1. 從NPM 服務騎上下載或拉去別人編寫好的第三方包到本地進行使用2. 將自己編寫代碼或軟件包發布到npm 服務器供他人使用…

基于Mediapipe_Unity_Plugin實現手勢識別

GitHub - homuler/MediaPipeUnityPlugin: Unity plugin to run MediaPipehttps://github.com/homuler/MediaPipeUnityPlugin 實現了以下&#xff1a; public enum HandGesture { None, Stop, ThumbsUp, Victory, OK, OpenHand } 核心腳本&#xff1a…

Android 項目構建編譯概述

主要內容是Android AOSP源碼的管理方式&#xff0c;項目源碼的構建和編譯&#xff0c;用到比如git、repo、gerrit一些命令工具&#xff0c;以及使用Soong編譯系統&#xff0c;編寫Android.bp文件的格式樣式。 1. Android操作系統堆棧概述 Android 是一個針對多種不同設備類型打…

Python爬蟲08_Requests聚焦批量爬取圖片

一、Requests聚焦批量爬取圖片 import re import requests import os import timeurl https://www.douban.com/ userAgent {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0}#獲取整個瀏覽頁面 page_text requests.get(urlur…

Spring Cloud系列—簡介

目錄 1 單體架構 2 集群與分布式 3 微服務架構 4 Spring Cloud 5 Spring Cloud環境和工程搭建 5.1 服務拆分 5.2 示例 5.2.1 數據庫配置 5.2.2 父子項目創建 5.2.3 order_service子項目結構配置 5.2.4 product_service子項目結構配置 5.2.5 服務之間的遠程調用 5.…

【普中STM32精靈開發攻略】--第 1 章 如何使用本攻略

學習本開發攻略主要參考的文檔有《STM32F1xx 中文參考手冊》和《Cortex M3權威指南(中文)》&#xff0c;這兩本都是 ST 官方手冊&#xff0c;尤其是《STM32F1xx 中文參考手冊》&#xff0c;里面包含了 STM32F1 內部所有外設介紹&#xff0c;非常詳細。大家在學習 STM32F103的時…

【Docker】RK3576-Debian上使用Docker安裝Ubuntu22.04+ROS2

1、簡述 RK3576自帶Debian12系統,如果要使用ROS2,可以在Debian上直接安裝ROS2,缺點是有的ROS包需要源碼編譯;當然最好是使用Ubuntu系統,可以使用Docker安裝,或者構建Ubuntu系統,替換Debian系統。 推薦使用Docker來安裝Ubuntu22.04,這里會有個疑問,是否可以直接使用Do…

解決docker load加載tar鏡像報json no such file or directory的錯誤

在使用docker加載離線鏡像文件時&#xff0c;出現了json no such file or directory的錯誤&#xff0c;剛開始以為是壓縮包拷貝壞了&#xff0c;重新拷貝了以后還是出現了問題。經過網上查找方案&#xff0c;并且自己實踐&#xff0c;采用下面的簡單方法就可以搞定。 歸結為一句…

《協作畫布的深層架構:React與TypeScript構建多人實時繪圖應用的核心邏輯》

多人在線協作繪圖應用的構建不僅是技術棧的簡單組合,更是對實時性、一致性與用戶體驗的多維挑戰。基于React與TypeScript開發這類應用,需要在圖形繪制的基礎功能之外,解決多用戶并發操作的同步難題、狀態回溯的邏輯沖突以及大規模協作的性能瓶頸。每一層架構的設計,都需兼顧…

智慧社區(八)——社區人臉識別出入管理系統設計與實現

在社區安全管理日益智能化的背景下&#xff0c;傳統的人工登記方式已難以滿足高效、精準的管理需求。本文將詳細介紹一套基于人臉識別技術的社區出入管理系統&#xff0c;該系統通過整合騰訊云 AI 接口、數據庫設計與業務邏輯&#xff0c;實現了居民出入自動識別、記錄追蹤與訪…

嵌入式開發學習———Linux環境下IO進程線程學習(四)

進程相關函數fork創建一個子進程&#xff0c;子進程復制父進程的地址空間。父進程返回子進程PID&#xff0c;子進程返回0。pid_t pid fork(); if (pid 0) { /* 子進程代碼 */ } else { /* 父進程代碼 */ }getpid獲取當前進程的PID。pid_t pid getpid();getppid獲取父進程的P…

標記-清除算法中的可達性判定與Chrome DevTools內存分析實踐

引言 在現代前端開發中&#xff0c;內存管理是保證應用性能與用戶體驗的核心技術之一。作為JavaScript運行時的基礎機制&#xff0c;標記-清除算法(Mark-and-Sweep) 通過可達性判定決定哪些內存需要回收&#xff0c;而Chrome DevTools提供的Memory工具則為開發者提供了深度的內…

微算法科技(NASDAQ:MLGO)基于量子重加密技術構建區塊鏈數據共享解決方案

隨著信息技術的飛速發展&#xff0c;數據已成為數字經濟時代的核心生產要素。數據的共享和安全往往是一對難以調和的矛盾。傳統的加密方法在面對日益強大的計算能力和復雜的網絡攻擊時&#xff0c;安全性受到了挑戰。微算法科技(NASDAQ&#xff1a;MLGO)通過引入量子重加密技術…

FastAPI快速入門P2:與SpringBoot比較

歡迎來到啾啾的博客&#x1f431;。 記錄學習點滴。分享工作思考和實用技巧&#xff0c;偶爾也分享一些雜談&#x1f4ac;。 有很多很多不足的地方&#xff0c;歡迎評論交流&#xff0c;感謝您的閱讀和評論&#x1f604;。 目錄引言1 FastAPI事件管理2 類的使用2.1 初始化方法對…

SAP-ABAP: Open SQL集合函數COUNT(統計行數)、SUM(數值求和)、AVG(平均值)、MAX/MIN(極值)深度指南

SAP Open SQL集合函數深度指南 1. 核心價值與特性函數作用關鍵特性COUNT統計行數用COUNT(*)包含NULL值行&#xff0c;COUNT(字段)排除NULLSUM數值求和自動過濾NULL值&#xff0c;結果類型與源字段相同AVG平均值必須用TYPE f接收&#xff0c;否則四舍五入導致精度丟失MAX/MIN極值…

【docker】UnionFS聯合操作系統

Linux 的 Namespace、CGroups 和 UnionFS 三大技術支撐了 Docker 的實現。 一、為什么需要聯合文件系統&#xff1f;在傳統操作系統中&#xff0c;每個文件系統都是獨立的孤島。但當我們需要&#xff1a;合并多個目錄的內容保持基礎系統不變的同時進行修改高效共享重復文件內容…