Go 的第一類對象與閉包

1. Go 的第一類對象(First-Class Citizens)

什么是第一類對象?

  • 第一類對象是指能夠像 普通值 一樣使用的對象,通常可以賦值給變量、傳遞給函數、作為函數返回值等。
  • 在很多編程語言中,函數本身不被視為第一類對象(例如 C),它們是通過函數指針或類似機制來操作而在 Go 中,函數被視為 第一類對象,意味著函數可以像其他數據類型一樣被處理

Go 中的第一類對象:

Go 語言將 函數 作為第一類對象,這使得它們可以:

  1. 作為 變量 被賦值和傳遞。
  2. 作為 參數 被傳遞給其他函數。
  3. 作為 返回值 從函數返回。
  4. 與其他數據類型(如 intstringstruct 等)一樣操作。

示例:函數作為第一類對象

package mainimport "fmt"// 定義一個簡單的函數
func add(a, b int) int {return a + b
}func main() {// 將函數賦值給變量var f func(int, int) intf = add  // 函數賦值給變量 f// 通過變量調用函數result := f(2, 3)fmt.Println("Result:", result)  // 輸出:Result: 5
}
  • 你可以將函數 add 賦值給變量 f,并通過變量 f 來調用 add 函數。
  • 函數 add 本質上是一個值,存儲在變量 f 中,f 是一個 函數類型的變量

2. 閉包(Closure)

什么是閉包?

閉包是一個函數,它不僅包含了函數的 代碼,還 捕獲保留 外部作用域中的變量。閉包讓函數可以訪問其外部函數的變量,即使外部函數已經返回,閉包仍然能夠使用這些變量。

在 Go 中,閉包是一種非常強大的概念,允許函數在其外部環境中“記住”并 操作 捕獲的變量。閉包使得 Go 支持許多 函數式編程 的特性,如高階函數、回調函數等。

閉包的關鍵特性

  1. 捕獲外部變量:閉包能夠捕獲并訪問定義它的函數外部的變量。
  2. 函數和數據綁定:閉包會把外部變量和函數綁定在一起,即使外部函數已經返回,閉包依然能訪問這些變量。
  3. 狀態保持:閉包允許函數保持對外部變量的引用,從而讓它們保持一個狀態。

閉包的創建

在 Go 中,閉包是通過 函數返回值 來創建的,返回的函數可以訪問外部函數的局部變量。

示例:閉包的基本使用

package mainimport "fmt"// 返回一個閉包
func makeCounter() func() int {count := 0return func() int {count++return count}
}func main() {// 創建閉包counter := makeCounter()// 每次調用閉包時,count 都會增加fmt.Println(counter()) // 輸出:1fmt.Println(counter()) // 輸出:2fmt.Println(counter()) // 輸出:3
}

解釋

  • makeCounter 函數返回一個閉包,這個閉包引用了 count 變量。
  • counter 是一個閉包,每次調用它時,它都會增加 count 并返回新的值。
  • count 變量是 捕獲的外部變量,即使 makeCounter 函數已經返回,閉包仍然能夠訪問和修改 count

3. 閉包的詳細工作原理

捕獲變量

  • 當 Go 創建一個閉包時,閉包會 捕獲 外部函數的變量,保留它們的引用,而不是拷貝它們的值。這使得閉包能夠保留對這些變量的訪問權,直到閉包不再使用這些變量為止。

生命周期和內存管理

  • Go 的垃圾回收機制會確保閉包的內存得到正確管理。如果閉包捕獲了某些變量,這些變量不會在閉包生命周期結束時被回收,直到閉包本身不再被引用。
  • 這使得閉包在需要持有外部狀態(如計數器、緩存等)時非常有用。
示例:閉包和外部變量的作用域
package mainimport "fmt"func main() {var counter int// 創建閉包,閉包引用外部變量 counterincrement := func() int {counter++return counter}// 調用閉包fmt.Println(increment()) // 輸出:1fmt.Println(increment()) // 輸出:2fmt.Println(increment()) // 輸出:3
}

解釋

  • 閉包 increment 每次調用時都會訪問并修改外部的 counter 變量,閉包保留了對外部變量 counter 的引用,每次調用時都增加 counter 的值。

4. 閉包和 Go 的內存管理

Go 的垃圾回收機制會確保閉包中的變量在不再使用時被正確清理。例如,在上面的 makeCounter 例子中,閉包 counter 持有對 count 變量的引用。只要 counter 被引用,count 就不會被垃圾回收。只有在 counter 不再被引用時,閉包才會釋放相關的內存。


5. 閉包的常見應用場景

  1. 回調函數和異步操作

    • 閉包在回調函數中廣泛使用,可以保持外部變量的狀態,尤其在異步操作和事件驅動編程中非常有用。
  2. 函數工廠

    • 閉包可用作 工廠函數,生成具有不同行為的函數。
  3. 狀態保持

    • 閉包非常適合實現需要持久狀態的邏輯,如 計數器緩存 等。
  4. 函數式編程模式

    • 閉包是實現 函數式編程(如高階函數)的基礎,允許函數返回另一個函數,或者使用函數作為參數

6.區分閉包與普通函數

在 Go 中,閉包(Closure)普通函數 之間的區別主要體現在它們是否捕獲外部變量的值。普通函數 沒有 捕獲外部變量,而閉包 會捕獲外部函數的局部變量

1. 閉包與普通函數的本質區別:

  • 普通函數:一個普通的函數,它的行為是固定的,不依賴于外部的變量或上下文。普通函數 沒有 捕獲外部變量的能力。
  • 閉包:一個函數,它捕獲并“記住”外部函數的變量,即使外部函數的作用域已經結束。閉包會持有對外部變量的引用,并且可以在函數外部繼續訪問這些變量。

2 實例區分:

普通函數
package mainimport "fmt"// 普通函數:不依賴外部變量,只根據輸入參數工作
func add(a, b int) int {return a + b
}func main() {fmt.Println(add(2, 3)) // 輸出 5
}

解釋:

  • 這個 add 函數是一個普通函數,它只根據輸入的 ab 進行計算,不依賴于任何外部的變量。
  • 它的行為 完全由輸入參數決定,不依賴于外部的狀態。
閉包
package mainimport "fmt"// 閉包:函數內部訪問并捕獲外部變量
func makeMultiplier(factor int) func(int) int {return func(x int) int {return x * factor // 使用外部捕獲的變量 `factor`}
}func main() {multiplyBy2 := makeMultiplier(2)  // 創建閉包,factor = 2multiplyBy3 := makeMultiplier(3)  // 創建閉包,factor = 3fmt.Println(multiplyBy2(5))  // 輸出 10fmt.Println(multiplyBy3(5))  // 輸出 15
}

解釋:

  • 這個 makeMultiplier 函數返回了一個閉包。這個閉包引用了外部變量 factor,并根據 factor 執行不同的計算。即使 makeMultiplier 函數已經返回,閉包仍然能夠 記住 factor 的值,并在后續的調用中使用它。
  • 這里的 multiplyBy2multiplyBy3 是兩個閉包,它們分別捕獲了 factor 的值 23

3. 如何通過代碼結構判斷:

  • 普通函數 通常是直接定義在包內或者文件中的獨立函數,且它們的參數和返回值類型是固定的,不依賴外部的變量。
  • 閉包 通常是由 內部函數 返回的,外部函數的局部變量在閉包中被捕獲并且可以繼續訪問。
示例:閉包與普通函數的結構對比
package mainimport "fmt"// 普通函數
func square(x int) int {return x * x
}// 閉包函數:捕獲并使用外部變量
func createAdder(y int) func(int) int {return func(x int) int {return x + y // 捕獲并使用外部變量 y}
}func main() {// 普通函數調用fmt.Println(square(4)) // 輸出 16// 閉包函數調用add5 := createAdder(5)  // 返回一個閉包fmt.Println(add5(10))    // 輸出 15,閉包捕獲了 y = 5
}

區別

  • square 是一個普通函數,它 不依賴外部變量,它只使用它的參數 x 來計算。
  • createAdder 返回一個閉包,閉包 捕獲并使用了外部函數的局部變量 y。每次調用 add5 都是通過閉包引用了 y = 5 這個值。

6. 注意點

  • 閉包捕獲的是變量的引用,而不是它的值。例如,如果一個閉包捕獲了一個變量,并且該變量在外部函數中發生了改變,閉包將訪問到變量的最新值。
package mainimport "fmt"func main() {x := 10increment := func() int {x++return x}fmt.Println(increment()) // 輸出:11fmt.Println(increment()) // 輸出:12
}

解釋:

  • 閉包 increment 捕獲了外部變量 x,并且每次調用閉包時,x 的值都會遞增。
  • x 不是在閉包創建時固定的值,而是 被引用,因此閉包可以改變它的值。

7. 總結

  • 普通函數:不依賴外部作用域的變量。它的輸入和輸出是完全由它的參數決定的,不會修改外部狀態。
  • 閉包:定義在 外部函數內部,并且 捕獲并持有外部函數的局部變量,即使外部函數執行完畢,閉包依然能夠訪問這些變量。

通過這些規則和結構,你可以輕松區分一個函數是閉包還是普通函數。如果你有更多問題或需要進一步的解釋,請告訴我!

7. 總結:Go 中的第一類對象與閉包

  • 第一類對象:Go 中的函數是第一類對象,它們可以賦值給變量、作為參數傳遞、作為返回值等,這使得 Go 的函數非常靈活。
  • 閉包:Go 的閉包是捕獲并保留外部作用域變量的函數。閉包可以訪問其定義時外部函數的局部變量,即使外部函數已經返回。閉包允許你保持狀態,并提供強大的功能,尤其在需要函數式編程的場景中。

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

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

相關文章

深度分析Android多線程編程

理解并正確運用多線程是構建高性能、流暢、響應迅速的 Android 應用的關鍵,但也充滿挑戰和陷阱。 核心挑戰:UI 線程(主線程)的限制 唯一性: Android 應用只有一個主線程,負責處理所有用戶交互(觸…

uniapp在app中關于解決輸入框鍵盤彈出后遮住輸入框問題

問題描述: uniapp的app中,當表單頁面過長時,點擊下方的輸入框時,彈出鍵盤后會把輸入框給擋住,導致看不到輸入內容。 解決方案: 在page.json中,找到此頁面的配置,加上style中的softin…

二分查找----5.尋找旋轉排序數組中的最小值

題目鏈接 /** 數組在某處進行旋轉,分割為兩個獨立的遞增區間,找出數組的最小值;特殊情況:若旋轉次數是數組長度的倍數,則數組不變 特點: 常規情況: 數組被分割為兩個獨立的子區間,左半區的最小值大于右半區的最大值 依據數組長度,mid可能落在左半區也有可能落在右半區,最小值在…

Eureka-服務注冊,服務發現

在遠程調用的時候&#xff0c;我們寫的url是寫死的。 String url "<http://127.0.0.1:9090/product/>" orderInfo.getProductId();當換個機器&#xff0c;或者新增個機器&#xff0c;導致ip變換&#xff0c;從而使得 url 發生了變化&#xff0c;接著就需要去…

ubuntu24的一些小問題

截圖Keyboard -> Keyboard Shortcus -> View and customize Shortcus如上&#xff0c;可以修改默認的快捷按鍵。比如截圖按鍵可以修改。 ibus輸入法無法&#xff0c;輸入V異常問題 也是困擾了很久&#xff0c;發現是這樣的&#xff1a;https://github.com/libpinyin/ibus…

Python Locust庫詳解:從入門到分布式壓力測試實戰

一、Locust核心優勢 作為一款基于Python的開源負載測試工具&#xff0c;Locust通過協程架構實現了高效資源利用。其獨特優勢體現在&#xff1a; 純Python腳本&#xff1a;用熟悉的語言定義用戶行為&#xff0c;支持條件判斷和復雜邏輯分布式擴展&#xff1a;單節點支持數千并發…

Redis數據類型與內部編碼

在Redis中通常普遍認為&#xff0c;使用redis的能進行查詢&#xff0c;插入&#xff0c;刪除&#xff0c;修改操作都是O(1)是因為他是利用hash表實現的&#xff0c;但是&#xff0c;背后的實現不一定是一個標準的hash表&#xff0c;它內部的數據類型還會有變數&#xff0c;不過…

03-netty基礎-多路復用select、poll、epoll

1 什么是多路復用多路復用&#xff08;Multiplexing&#xff09; 是一種讓單個線程同時處理多個 I/O 通道的技術&#xff0c;核心是通過系統調用將 I/O 狀態查詢的工作交給操作系統內核&#xff0c;應用程序只需等待內核通知哪些通道就緒。多路&#xff1a;指的是多個socket網絡…

網易大模型算法面經總結第一篇

網友一 MHA的原理&#xff0c;是如何進行加速的&#xff0c;用的什么框架推理。 回答&#xff1a; ①先答一下什么是MHA&#xff1a;Multi-Head Attention&#xff08;MHA&#xff09;是 Transformer 的核心機制&#xff0c;并行地關注輸入序列中不同位置的多種信息 ②回答MHA的…

Vue3 面試題及詳細答案120道(91-105 )

《前后端面試題》專欄集合了前后端各個知識模塊的面試題&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

SAP-MM-物料進銷存表

ABAP庫存進銷存報表程序摘要 該ABAP程序是一個完整的庫存進銷存報表系統,主要功能包括: 報表類型選擇: 物料庫存進銷存 批次庫存進銷存 寄售庫存進銷存 供應商庫存進銷存 原料庫存進銷存 主要功能: 從歷史數據表(MARDH, MSKAH, MSLBH, MCHBH等)獲取期初庫存 處理物料移動數…

這幾天都是發癲寫的

#include <iostream> #include <vector> #include <unordered_map> #include <algorithm> #include <cmath> // for sqrt// Gen-Sort 實現&#xff08;保持不變&#xff09; void genSort(std::vector<int>& arr) {if (arr.empty()) r…

QT6 源,七章對話框與多窗體(11) 進度對話框 QProgressDialog:屬性,公共成員函數,槽函數,信號函數,與源代碼帶注釋

&#xff08;1&#xff09; 本類的繼承關系 &#xff1a;可見&#xff0c;進度對話框&#xff0c;也是 QDialog 的子類&#xff0c;在其上面又擺放了一些控件&#xff0c;構成了不同用途的對話框。咱們也可以自定義對話框。只是沒有 QT 官方大師們做的好。 人家在定義這 6 個子…

學習游戲制作記錄(技能系統)7.24

1.技能系統概念首先讓我們了解一下游戲的技能本質是什么&#xff0c;以投擲劍為例子&#xff0c;當玩家使用這個技能時&#xff0c;首先會播放玩家的動畫&#xff0c;隨后通過技能腳本創建一個劍的對象&#xff0c;當劍回收時會再次調用腳本&#xff0c;讓它朝向玩家飛來并銷毀…

外部存檔(External Archive)機制

前言 提醒&#xff1a; 文章內容為方便作者自己后日復習與查閱而進行的書寫與發布&#xff0c;其中引用內容都會使用鏈接表明出處&#xff08;如有侵權問題&#xff0c;請及時聯系&#xff09;。 其中內容多為一次書寫&#xff0c;缺少檢查與訂正&#xff0c;如有問題或其他拓展…

MybatisPlus操作方法詳細總結

摘要&#xff1a;本文圍繞 MyBatis-Plus 數據操作展開&#xff0c;涵蓋標準數據層 CRUD 與分頁查詢&#xff1b;以及各種的復雜 SQL 查詢&#xff1b;映射匹配&#xff08;TableField、TableName 注解&#xff09;與 ID 生成策略&#xff08;TableId 五種類型及全局配置&#x…

【C語言進階】動態內存管理的面試題||練習

本節內容專門整理了一些動態內存管理的面試題&#xff0c;配有詳細的解答。 目錄 1. 看代碼說結果 2. 看代碼說結果 3. 看代碼說結果 4.小樂樂與歐幾里得 描述 分析1&#xff1a; 分析2&#xff1a; 代碼&#xff1a; 5. 空心正方形 分析&#xff1a; 1. 看代碼說結…

【圖論】倍增與lca

void dfs(long u,long father){ dep[u]dep[father]1;//只在這里初始化depfor(long i1;(1<<i)<dep[u];i)fa[u][i]fa[fa[u][i-1]][i-1];//只這里用的倍增for(long ihead[u];~i;iedge[i].next){long vedge[i].to;if(vfather)continue;fa[v][0]u;dfs(v,u); }} long lca(lo…

VS Code 美化插件

目錄1. Better Comments 更好的注釋2. indent-rainbow 彩虹的縮進3. Trailing Spaces 尾隨的空格4. Gruvbox Material 護眼的材質5. Md Editor 博客編輯器6. 待補充推薦筆記&#xff1a;VS Code寫代碼必備的五款代碼美化插件 1. Better Comments 更好的注釋 Better Comments Be…

火語言 RPA 在日常運維中的實踐

在系統運維和技術支持工作中&#xff0c;總有一些操作像 “固定程序” 一樣循環往復&#xff1a;定期檢查服務器狀態、批量處理用戶權限申請、手動清理系統日志…… 這些工作步驟固定、邏輯簡單&#xff0c;卻占用了大量本可用于故障排查和系統優化的時間。近期在優化運維團隊的…