最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文徹底理解Golang閉包實現(xiàn)原理

 更新時間:2022年10月26日 08:18:10   作者:crossoverJie  
閉包對于一個長期寫Java的開發(fā)者來說估計鮮有耳聞,光這名字感覺就有點"神秘莫測"。這篇文章的主要目的就是從編譯器的角度來分析閉包,徹底搞懂閉包的實現(xiàn)原理,需要的可以參考一下

前言

閉包對于一個長期寫 Java 的開發(fā)者來說估計鮮有耳聞,我在寫 Python 和 Go 之前也是沒怎么了解,光這名字感覺就有點"神秘莫測",這篇文章的主要目的就是從編譯器的角度來分析閉包,徹底搞懂閉包的實現(xiàn)原理。

函數(shù)一等公民

一門語言在實現(xiàn)閉包之前首先要具有的特性就是:First class function 函數(shù)是第一公民。

簡單來說就是函數(shù)可以像一個普通的值一樣在函數(shù)中傳遞,也能對變量賦值。

先來看看在 Go 里是如何編寫的:

package main

import "fmt"

var varExternal int

func f1() func(int) int {
	varInner := 20
	innerFun := func(a int) int {
		fmt.Println(a)
		varExternal++
		varInner++
		return varInner
	}
	return innerFun
}

func main() {
	varExternal = 10
	f2 := f1()
	for i := 0; i < 2; i++ {
		fmt.Printf("varInner=%d, varExternal=%d \n", f2(i), varExternal)
	}
	fmt.Println("======")

	f3 := f1()
	for i := 0; i < 2; i++ {
		fmt.Printf("varInner=%d, varExternal=%d \n", f3(i), varExternal)
	}
}

// Output:
0
varInner=21, varExternal=11 
1
varInner=22, varExternal=12 
======
0
varInner=21, varExternal=13 
1
varInner=22, varExternal=14 

這里體現(xiàn)了閉包的兩個重要特性,第一個自然就是函數(shù)可以作為值返回,同時也能賦值給變量。

第二個就是在閉包函數(shù) f1() 對閉包變量 varInner 的訪問,每個閉包函數(shù)的引用都會在自己的函數(shù)內(nèi)部保存一份閉包變量 varInner,這樣在調(diào)用過程中就不會互相影響。

從打印的結(jié)果中也能看出這個特性。

作用域

閉包之所以不太好理解的主要原因是它不太符合自覺。

本質(zhì)上就是作用域的關(guān)系,當(dāng)我們調(diào)用 f1() 函數(shù)的時候,會在棧中分配變量 varInner,正常情況下調(diào)用完畢后 f1 的棧會彈出,里面的變量 varInner 自然也會銷毀才對。

但在后續(xù)的 f2() 和 f3() 調(diào)用的時,卻依然能訪問到 varInner,就這點不符合我們對函數(shù)調(diào)用的直覺。

但其實換個角度來看,對 innerFun 來說,他能訪問到 varExternal 和 varInner 變量,最外層的 varExternal 就不用說了,一定是可以訪問的。

但對于 varInner 來說就不一定了,這里得分為兩種情況;重點得看該語言是靜態(tài)/動態(tài)作用域。

就靜態(tài)作用域來說,每個符號在編譯器就確定好了樹狀關(guān)系,運行時不會發(fā)生變化;也就是說 varInner 對于 innerFun 這個函數(shù)來說在編譯期已經(jīng)確定可以訪問了,在運行時自然也是可以訪問的。

但對于動態(tài)作用域來說,完全是在運行時才確定訪問的變量是哪一個。

恰好 Go 就是一個靜態(tài)作用域的語言,所以返回的 innerFun 函數(shù)可以一直訪問到 varInner 變量。

實現(xiàn)閉包

但 Go 是如何做到在 f1() 函數(shù)退出之后依然能訪問到 f1() 中的變量呢?

這里我們不妨大膽假設(shè)一下:

首先在編譯期掃描出哪些是閉包變量,也就是這里的 varInner,需要將他保存到函數(shù) innerFun() 中。

f2 := f1()
f2()

運行時需要判斷出 f2 是一個函數(shù),而不是一個變量,同時得知道它所包含的函數(shù)體是 innerFun() 所定義的。

接著便是執(zhí)行函數(shù)體的 statement 即可。

而當(dāng) f3 := f1() 重新賦值給 f3 時,在 f2 中累加的 varInner 變量將不會影響到 f3,這就得需要在給 f3 賦值的重新賦值一份閉包變量到 f3 中,這樣便能達(dá)到互不影響的效果。

閉包掃描

GScript 本身也是支持閉包的,所以把 Go 的代碼翻譯過來便長這樣:

int varExternal =10;
func int(int) f1(){
	int varInner = 20;
	int innerFun(int a){
		println(a);
		int c=100;
		varExternal++;
		varInner++;
		return varInner;
	}
	return innerFun;
}

func int(int) f2 = f1();
for(int i=0;i<2;i++){
	println("varInner=" + f2(i) + ", varExternal=" + varExternal);
}
println("=======");
func int(int) f3 = f1();
for(int i=0;i<2;i++){
	println("varInner=" + f3(i) + ", varExternal=" + varExternal);
}

// Output:
0
varInner=21, varExternal=11
1
varInner=22, varExternal=12
=======
0
varInner=21, varExternal=13
1
varInner=22, varExternal=14

可以看到運行結(jié)果和 Go 的一樣,所以我們來看看 GScript 是如何實現(xiàn)的便也能理解 Go 的原理了。

先來看看第一步掃描閉包變量:

allVariable := c.allVariable(function)查詢所有的變量,包括父 scope 的變量。

scopeVariable := c.currentScopeVariable(function)查詢當(dāng)前 scope 包含下級所有 scope 中的變量,這樣一減之后就能知道閉包變量了,然后將所有的閉包變量存放進(jìn)閉包函數(shù)中。

閉包賦值

之后在 return innerFun 處,將閉包變量的數(shù)據(jù)賦值到變量中。

閉包函數(shù)調(diào)用

func int(int) f2 = f1();

func int(int) f3 = f1();

在這里每一次賦值時,都會把 f1() 返回函數(shù)復(fù)制到變量 f2/f3 中,這樣兩者所包含的閉包變量就不會互相影響。

在調(diào)用函數(shù)變量時,判斷到該變量是一個函數(shù),則直接返回函數(shù)。

之后直接調(diào)用該函數(shù)即可。

函數(shù)式編程

接下來便可以利用 First class function 來試試函數(shù)式編程:

class Test{
	int value=0;
	Test(int v){
		value=v;
	}

	int map(func int(int) f){
		return f(value);
	}
}
int square(int v){
	return v*v; 
}
int add(int v){
	return v++; 
}
int add2(int v){
	v=v+2;
	return v; 
}
Test t =Test(100);
func int(int) s= square;
func int(int) a= add;
func int(int) a2= add2;
println(t.map(s));
assertEqual(t.map(s),10000);

println(t.map(a));
assertEqual(t.map(a),101);

println(t.map(a2));
assertEqual(t.map(a2),102);

這個有點類似于 Java 中流的 map 函數(shù),將函數(shù)作為值傳遞進(jìn)去,后續(xù)支持匿名函數(shù)后會更像是函數(shù)式編程,現(xiàn)在必須得先定義一個函數(shù)變量再進(jìn)行傳遞。

除此之外在 GScript 中的 http 標(biāo)準(zhǔn)庫也利用了函數(shù)是一等公民的特性:

// 標(biāo)準(zhǔn)庫:Bind route
httpHandle(string method, string path, func (HttpContext) handle){
    HttpContext ctx = HttpContext();
    handle(ctx);
}

在綁定路由時,handle 便是一個函數(shù),使用的時候直接傳遞業(yè)務(wù)邏輯的 handle 即可:

func (HttpContext) handle (HttpContext ctx){
    Person p = Person();
    p.name = "abc";
    println("p.name=" + p.name);
    println("ctx=" + ctx);
    ctx.JSON(200, p);
}
httpHandle("get", "/p", handle);

總結(jié)

總的來說閉包具有以下特性:

  • 函數(shù)需要作為一等公民。
  • 編譯期掃描出所有的閉包變量。
  • 在返回閉包函數(shù)時,為閉包變量賦值。
  • 每次創(chuàng)建新的函數(shù)變量時,需要將閉包數(shù)據(jù)復(fù)制進(jìn)去,這樣閉包變量才不會互相影響。
  • 調(diào)用函數(shù)變量時,需要判斷為函數(shù),而不是變量。

可以在 Playground 中體驗閉包函數(shù)打印裴波那切數(shù)列的運用。

到此這篇關(guān)于一文徹底理解Golang閉包實現(xiàn)原理 的文章就介紹到這了,更多相關(guān)Golang閉包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang unsafe.Sizeof函數(shù)代碼示例使用解析

    Golang unsafe.Sizeof函數(shù)代碼示例使用解析

    這篇文章主要為大家介紹了Golang unsafe.Sizeof函數(shù)代碼示例使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 一文解析 Golang sync.Once 用法及原理

    一文解析 Golang sync.Once 用法及原理

    這篇文章主要介紹了一文解析 Golang sync.Once 用法及原理,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • 簡單聊聊Go?for?range中容易踩的坑

    簡單聊聊Go?for?range中容易踩的坑

    for循環(huán)問題,在面試中經(jīng)常都會被問到,并且在實際業(yè)務(wù)項目中也經(jīng)常用到for循環(huán),要是沒用好,一不下心就掉坑,本文就來講講Go?for?range中容易踩的坑吧
    2023-03-03
  • Go語言學(xué)習(xí)教程之反射的示例詳解

    Go語言學(xué)習(xí)教程之反射的示例詳解

    這篇文章主要通過記錄對reflect包的簡單使用,來對反射有一定的了解。文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定幫助,需要的可以參考一下
    2022-09-09
  • Go語言中l(wèi)og日志庫的介紹

    Go語言中l(wèi)og日志庫的介紹

    本文給大家介紹Go語言中l(wèi)og日志庫的概念使用技巧,log包定義了Logger類型,該類型提供了一些格式化輸出的方法,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-10-10
  • Golang中常見的三種并發(fā)控制方式使用小結(jié)

    Golang中常見的三種并發(fā)控制方式使用小結(jié)

    這篇文章主要為大家詳細(xì)介紹了如何對goroutine并發(fā)行為的控制,在Go中最常見的有三種方式:sync.WaitGroup、channel和Context,下面我們就來看看他們的具體使用吧
    2024-01-01
  • 淺談Go語言的error類型

    淺談Go語言的error類型

    這篇文章主要介紹了淺談Go語言的error類型,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 詳解Go語言如何實現(xiàn)并發(fā)安全的map

    詳解Go語言如何實現(xiàn)并發(fā)安全的map

    go語言提供的數(shù)據(jù)類型中,只有channel是并發(fā)安全的,基礎(chǔ)map并不是并發(fā)安全的,本文為大家整理了三種實現(xiàn)了并發(fā)安全的map的方案,有需要的可以參考下
    2023-12-12
  • 基于Go+WebSocket實現(xiàn)實時通信功能

    基于Go+WebSocket實現(xiàn)實時通信功能

    在互聯(lián)網(wǎng)應(yīng)用程序中,實時通信是一種非常重要的功能,WebSocket 是一種基于 TCP 的協(xié)議,它允許客戶端和服務(wù)器之間進(jìn)行雙向通信,本文將介紹如何使用 Golang 創(chuàng)建單獨的 WebSocket 會話,以實現(xiàn)實時通信功能,需要的朋友可以參考下
    2023-10-10
  • 基于Go語言實現(xiàn)一個壓測工具

    基于Go語言實現(xiàn)一個壓測工具

    這篇文章主要為大家詳細(xì)介紹了基于Go語言實現(xiàn)一個簡單的壓測工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-01-01

最新評論

佛坪县| 吉木萨尔县| 葫芦岛市| 嘉义市| 泸溪县| 浑源县| 电白县| 介休市| 高邑县| 临邑县| 诸城市| 潼关县| 从江县| 宜君县| 宜良县| 车险| 常山县| 繁昌县| 龙南县| 慈利县| 大悟县| 汾西县| 平定县| 北碚区| 马鞍山市| 吉首市| 饶平县| 阳信县| 安塞县| 灌南县| 潞西市| 封丘县| 峡江县| 额敏县| 咸宁市| 昭觉县| 尤溪县| 乌兰县| 蒙城县| 洛浦县| 文成县|