Go Function

  • 函數(shù)是組織良好且可重復(fù)使用的以蕴,用來實(shí)現(xiàn)單一或相關(guān)功能的代碼塊朝氓,用于提高模塊化和復(fù)用性皆刺。
  • 編寫函數(shù)的目的是為了將需要多行代碼實(shí)現(xiàn)的復(fù)雜問題分解為一系列簡單的任務(wù)來解決针饥。

Go語言是編譯型的,因此函數(shù)的順序和位置是無關(guān)緊要的浪秘,鑒于可讀性推薦將main主函數(shù)編寫在文件前面蒋情,其它函數(shù)按照一定邏輯順序向下編寫。

Go語言支持普通函數(shù)耸携、匿名函數(shù)棵癣、閉包,從設(shè)計(jì)上對函數(shù)進(jìn)行了優(yōu)化和改進(jìn)夺衍。

Go語言中函數(shù)是一等公民(first-class)狈谊,函數(shù)本身可作為值來傳遞,函數(shù)支持匿名函數(shù)和閉包,函數(shù)可以滿足接口河劝。

聲明函數(shù)

函數(shù)構(gòu)成了代碼執(zhí)行的邏輯結(jié)構(gòu)壁榕,是程序的基本模塊。

Go語言中函數(shù)由關(guān)鍵字func赎瞎、函數(shù)名牌里、參數(shù)列表、返回值务甥、函數(shù)體二庵、返回語句構(gòu)成。

func fnname(argslist)(retlist){
  //fnbody
}
  • 形參列表argslist 描述了函數(shù)的參數(shù)名以及參數(shù)類型缓呛,參數(shù)屬于局部變量催享,參數(shù)值由調(diào)用者提供。
  • 返回值列表retlist 描述了函數(shù)返回值的名稱以及類型哟绊,若無返回值或返回一個無名的變量因妙,返回值列表的括號可省略。
  • 若函數(shù)聲明中不包括返回值列表則函數(shù)體執(zhí)行完畢后不會有返回值

例如:求直角三角形斜邊票髓,勾股定理攀涵,勾三股四弦五。

package main

import (
    "fmt"
    "math"
)

func hypot(x, y float64) float64{
    return math.Sqrt(x*x + y*y)
}

func main() {
    fmt.Println(hypot(3, 4))//5
}

若形參或返回值具有相同類型洽沟,不必針對每個形參都添加參數(shù)類型以故。

func fn(i, j, k int, s, t string){}

形參可使用_空白標(biāo)識符用來強(qiáng)調(diào)參數(shù)未被使用

package main

import "fmt"

func add(x, y int) int     {return x + y}
func sub(x, y int) (z int) {return x - y}
func first(x, _ int) int   {return x}
func zero(int, int) int    {return 0}

func main() {
    fmt.Printf("x + y = %d\n", add(1, 2))//x + y = 3
    fmt.Printf("x - y = %d\n", sub(1, 2))//x - y = -1
    fmt.Printf("first = %d\n", first(1, 2))//first = 3
    fmt.Printf("zero = %d\n", zero(1, 2))//zero = 0
}

當(dāng)函數(shù)執(zhí)行到代碼塊最后一行之前或是return語句時退出

函數(shù)類型

Go語言中函數(shù)是一種類型,因此可以和其他類型一樣保存在變量中裆操。

package main

import "fmt"

//定義函數(shù)
func fire(){
    fmt.Printf("fire")
}

func main() {
    //將變量聲明為函數(shù)類型func()怒详,變量fn即回調(diào)函數(shù),此時fn的值為nil踪区。
    var fn func()
    fmt.Printf("fn = %v, type = %T\n", fn, fn)//fn = <nil>, type = func()
    //將fire()函數(shù)作為值賦給函數(shù)變量fn昆烁,此時fn的只也就是fire()函數(shù)。
    fn = fire
    fmt.Printf("fn = %v, type = %T\n", fn, fn)//fn = 0x1076fe0, type = func()
    //使用函數(shù)變量fn執(zhí)行函數(shù)調(diào)用缎岗,實(shí)際調(diào)用的是fire()函數(shù)
    fn()//fire
}

函數(shù)類型又稱為函數(shù)的標(biāo)識符

package main

import "fmt"

func fn(x, y int) int     {return x + y}

func main() {
    fmt.Printf("%T\n", fn)//func(int, int) int
}

若兩個函數(shù)形參列表和返回值列表中的變量類型一一對應(yīng)静尼,則這兩個函數(shù)被認(rèn)為具有相同的類型和標(biāo)識符。

package main

import "fmt"

func add(x, y int) int {return x + y}
func sub(x, y int) int {return x - y}

func main() {
    fmt.Printf("%T\n", add)//func(int, int) int
    fmt.Printf("%T\n", sub)//func(int, int) int
}

形參和返回值的變量名并不會影響函數(shù)標(biāo)識符传泊,也不會影響參數(shù)類型的省略鼠渺。

函數(shù)在每次調(diào)用時必須按照聲明時的順序?yàn)樗袇?shù)提供實(shí)參(參數(shù)值)

Go語言中形參沒有默認(rèn)的參數(shù)值,也沒有任何方法通過參數(shù)名指定形參眷细,因此形參和返回值的變量名對于函數(shù)調(diào)用者而言是沒有意義的拦盹。

函數(shù)中實(shí)參通過值傳遞的方式進(jìn)行賦值,因此函數(shù)形參是實(shí)參的拷貝薪鹦,因此對形參進(jìn)行修改不會影響到實(shí)參掌敬。不過若實(shí)參包含引用類型,比如指針池磁、切片奔害、映射、函數(shù)地熄、通道等华临,實(shí)參則可能會由于函數(shù)的間接引用而被修改。

多返回值

Go語言中函數(shù)支持多返回值端考,多返回值能夠方便地獲得函數(shù)執(zhí)行后的多個參會參數(shù)雅潭。

Go語言會使用多返回值的最后一個返回參數(shù),返回函數(shù)執(zhí)行中可能發(fā)生的錯誤却特。

與其它語言的返回值相比

  • C/C++ 語言中只支持一個返回值扶供,當(dāng)需要多值返回時可使用結(jié)構(gòu)體。也可在參數(shù)中使用指針變量裂明,然后在函數(shù)內(nèi)部修改外部傳入的變量值椿浓,實(shí)際返回計(jì)算結(jié)果。
  • C++ 語言為了安全性闽晦,建議在參數(shù)返回?cái)?shù)據(jù)時使用引用替代指針扳碍。
  • C# 語言中沒有多返回值,C#語言后期加入的refout關(guān)鍵字能夠通過函數(shù)的調(diào)用參數(shù)以獲得函數(shù)體中修改的數(shù)據(jù)仙蛉。
  • Lua語言中雖然沒有指針但支持多返回值笋敞,特別適用于大塊數(shù)據(jù)。

Go語言既支持安全指針荠瘪,也支持多返回值夯巷,因此在使用函數(shù)進(jìn)行邏輯編寫時更加方便。

  • 同一類型返回值

若返回值是同一類型哀墓,則可使用括號()將多個返回值類型括起來鞭莽,用逗號,分隔每個返回值的類型。在函數(shù)體內(nèi)return語句返回時麸祷,值列表的順序需要和函數(shù)聲明的返回值類型保持一致澎怒。

package main

import "fmt"

func swap(first, last string) (string, string){
    return last, first
}

func main() {
    surname, name := swap("Mahesh", "Kumar")
    fmt.Printf("%s %s\n", surname, name)// Kumar Mahesh
}

純類型的返回值對于代碼的可讀性并不是很友好,特別是在同類型的返回值出現(xiàn)時阶牍,會無法區(qū)分每個返回值參數(shù)的含義喷面。

  • 帶變量名的返回值

Go語言支持對返回值進(jìn)行命名,這樣返回值和參數(shù)一樣擁有參數(shù)變量名稱和類型走孽。

待命名的返回值變量的默認(rèn)值是其類型的默認(rèn)值

例如:根據(jù)矩形的長和寬計(jì)算周長和面積

package main

import "fmt"

//對函數(shù)返回值進(jìn)行命名
func rect(length, width float64) (area, perimeter float64){
    //已命名返回值的變量與函數(shù)局部變量一樣惧辈,可以對返回值變量進(jìn)行賦值和值獲取。
    area = length * width
    perimeter = (length + width) * 2
    //當(dāng)函數(shù)使用命名返回值時在return中可以不填寫返回值列表磕瓷,若填寫也可以盒齿。
    return
}

func main() {
    area, perimeter := rect(10, 20)
    fmt.Printf("area = %f, perimeter = %f\n", area, perimeter)//area = 200.000000, perimeter = 60.000000
}

同一類型返回值和命名返回值兩種類型念逞,只能二選一不能混用典鸡,混用時將會發(fā)生編譯錯誤摆碉。

// syntax error: mixed named and unnamed function parameters
func rect(length, width float64) (area, perimeter float64, float64){
    area = length * width
    perimeter = (length + width) * 2
    return
}

語法錯誤:在函數(shù)參數(shù)中混合使用了命名和非命名參數(shù)

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

函數(shù)定義后可通過調(diào)用的方式讓當(dāng)前代碼跳轉(zhuǎn)到被調(diào)用的函數(shù)中去執(zhí)行,調(diào)用前的函數(shù)局部變量會被保存起來不會丟失衙伶,被調(diào)用的函數(shù)運(yùn)行結(jié)束后符匾,會恢復(fù)到調(diào)用函數(shù)的下一行繼續(xù)執(zhí)行代碼叨咖,之前的局部變量也能繼續(xù)訪問。

函數(shù)內(nèi)的局部變量只能在函數(shù)體中使用啊胶,函數(shù)調(diào)用結(jié)束后甸各,這些局部變量都會被釋放因此會失效。

Go語言中函數(shù)調(diào)用的格式

返回值變量列表 = 函數(shù)名(參數(shù)列表)
  • 變量名:需要調(diào)用的函數(shù)的名稱
  • 參數(shù)列表:參數(shù)變量以逗號分隔焰坪,尾部無須使用分號趣倾。
  • 返回值變量列表:多個返回值時需使用逗號分隔

匿名函數(shù)

Go語言支持匿名函數(shù),即在需要使用函數(shù)時定義的函數(shù)某饰,匿名函數(shù)不包含函數(shù)名誊酌,可用于創(chuàng)建內(nèi)聯(lián)函數(shù)。

Go語言中匿名函數(shù)可形成閉包露乏,匿名函數(shù)又稱為函數(shù)字面量碧浊。

匿名函數(shù)沒有函數(shù)名只有函數(shù)體,函數(shù)可以作為一種類型被賦值給函數(shù)類型的變量瘟仿。

匿名函數(shù)往往會以變量的方式進(jìn)行傳遞箱锐,與C語言的回調(diào)函數(shù)類似,不同的是Go語言支持隨時在代碼中定義匿名函數(shù)劳较。

匿名函數(shù)不需要定義函數(shù)名稱的一種函數(shù)實(shí)現(xiàn)方式驹止,由一個不帶函數(shù)名的函數(shù)聲明和函數(shù)體組成。

定義匿名函數(shù)

func(參數(shù)列表) (返回值列表) {
  函數(shù)體
}

匿名函數(shù)的定義簡單來說就是沒有名字的普通函數(shù)定義

在定義時調(diào)用匿名函數(shù)

匿名函數(shù)可以在聲明后調(diào)用观蜗,即自執(zhí)行函數(shù)臊恋。

package main

import "fmt"

func main() {
    func(data int){
        fmt.Printf("data = %v\n", data)// data = 1000
    }(1000)
}

將匿名函數(shù)賦值給變量

package main

import "fmt"

func main() {
    fn := func(data int){
        fmt.Printf("data = %v\n", data)// data = 1000
    }
    fn(1000)
}

匿名函數(shù)本身就是一種值,可以方便地保存在各種容器中實(shí)現(xiàn)回調(diào)函數(shù)和操作封裝墓捻。

匿名函數(shù)當(dāng)作回調(diào)函數(shù)

例如:對切片遍歷時訪問每個元素

package main

import "fmt"

func loop(slice []int, fn func(int)){
    for _,v := range slice{
        fn(v)
    }
}

func main() {
    slice := []int{1, 2, 3, 4}
    loop(slice, func(item int){
        fmt.Printf("item = %v\n", item)
    })
}

例如:對字符串左右兩邊的空格進(jìn)行去除

使用匿名函數(shù)實(shí)現(xiàn)操作封裝

例如:將匿名函數(shù)作為map的鍵值抖仅,通過命令行參數(shù)動態(tài)調(diào)用匿名函數(shù)。

package main

import (
    "flag"
    "fmt"
)

//定義命令行參數(shù)type砖第,在命令行輸入--type可其后的字符串傳入params指針變量中撤卢。
var params = flag.String("type", "", "type comment")

func main() {
    //解析命令行參數(shù),解析完畢后params指針變量將指向命令行傳入的值梧兼。
    flag.Parse()
    //定義從字符串映射到函數(shù)的map放吩,然后填充。
    typeMap := map[string] func(){
        //初始化map的鍵值對羽杰,值為匿名函數(shù)渡紫。
        "fire":func(){
            fmt.Printf("fire")
        },
        "run": func(){
            fmt.Printf("run")
        },
    }
    //typeMap是一個*string類型的指針變量
    //使用*params獲取命令行傳入的值并在map中查找對應(yīng)命令行參數(shù)指定的字符串函數(shù)
    if fn,ok := typeMap[*params]; ok{
        //若在map定義中存在參數(shù)則調(diào)用函數(shù)
        fn()
    }else{
        fmt.Printf("not found")
    }
}

運(yùn)行測試

$ go run test.go --type fire
fire

使用函數(shù)實(shí)現(xiàn)接口

函數(shù)在數(shù)據(jù)類型中屬于一等公民到推,其他類型能夠?qū)崿F(xiàn)接口,函數(shù)也可以惕澎。

例如:使用結(jié)構(gòu)體實(shí)現(xiàn)接口

package main

import "fmt"

//定義調(diào)用器接口
type Invoker interface {
    Call(interface{})//待實(shí)現(xiàn)調(diào)用方法
}

//定義結(jié)構(gòu)體類型
type Struct struct {

}

//實(shí)現(xiàn)Invoker調(diào)用程序的Call調(diào)用方法莉测,可傳入任意類型interface{}的值。
func (this *Struct) Call(param interface{}){
    fmt.Println(param)
}

func main() {
    //聲明接口變量
    var invoker Invoker
    //實(shí)例化結(jié)構(gòu)體類型
    s := new(Struct)
    //將結(jié)構(gòu)體實(shí)例賦值給接口
    invoker = s
    //使用接口變量調(diào)用結(jié)構(gòu)體實(shí)例方法
    invoker.Call("hello")
}

例如:使用函數(shù)實(shí)現(xiàn)接口

函數(shù)聲明不能直接實(shí)現(xiàn)接口集灌,需要將函數(shù)定義為類型后,使用類型實(shí)現(xiàn)結(jié)構(gòu)體复哆。當(dāng)類型方法被調(diào)用時欣喧,還需調(diào)用函數(shù)本體。

package main

import "fmt"

//定義調(diào)用者接口
type Invoker interface {
    Call(interface{})//待實(shí)現(xiàn)接口
}

//定義函數(shù)為類型
type Caller func(interface{})

//實(shí)現(xiàn)調(diào)用者方法
func (fn Caller) Call(param interface{}) {
    fn(param)
}

func main() {
    //聲明接口變量
    var invoker Invoker
    //將匿名函數(shù)轉(zhuǎn)換為Caller類型后再賦值給接口變量
    invoker = Caller(func(v interface{}) {
        fmt.Println(v)
    })
    //使用接口變量調(diào)用Caller類型的Call方法梯找,內(nèi)部會調(diào)用函數(shù)本體唆阿。
    invoker.Call("hello")
}

閉包

閉包又稱為詞法閉包(lexical closure)或函數(shù)閉包(function closure),是函數(shù)式編程語言中用于實(shí)現(xiàn)詞法范圍的名稱綁定技術(shù)锈锤。 閉包并非某種語言特有的機(jī)制驯鳖,只是經(jīng)常會出現(xiàn)在函數(shù)式編程語言中,因?yàn)楹瘮?shù)式編程語言中函數(shù)是一等公民(first-class)久免。

從操作實(shí)現(xiàn)上來將浅辙,閉包是將函數(shù)及其運(yùn)行環(huán)境(引用環(huán)境)打包存儲的一條記錄。函數(shù)的運(yùn)行環(huán)境又稱為執(zhí)行上下文(execution context)阎姥,包括函數(shù)運(yùn)行時所處的內(nèi)部環(huán)境和所依賴的外部環(huán)境记舆。

Go語言中匿名函數(shù)可作為閉包,閉包和普通函數(shù)的區(qū)別在于呼巴,普通函數(shù)被調(diào)用者執(zhí)行完畢后會丟棄環(huán)境泽腮,而閉包則依然會保留運(yùn)行環(huán)境。

函數(shù)的運(yùn)行環(huán)境只是一種映射衣赶,它會將函數(shù)的每個自由變量與創(chuàng)建閉包時名稱綁定的值或引用相互關(guān)聯(lián)诊赊。函數(shù)的自由變量特指在本地使用,但卻在封閉的范圍內(nèi)定義的變量府瞄。

自由變量是相當(dāng)于閉包或匿名函數(shù)而言的外部變量碧磅,由于該變量的定義不受自身控制,因此對閉包自身來說是自由的遵馆,因?yàn)椴粫艿介]包的約束续崖。

與普通函數(shù)不同的是閉包允許函數(shù)通過閉包的值副本或引用來訪問那些被捕獲的變量,即使函數(shù)在其作用域之外被調(diào)用团搞。也就是說閉包提供了一種可持續(xù)訪問被捕獲變量的能力严望,進(jìn)而擴(kuò)大了變量的作用域。

閉包提供了持續(xù)暴露變量的機(jī)制逻恐,使外界能夠訪問原本應(yīng)該私有的變量像吻,實(shí)現(xiàn)了全局變量的作用域效果峻黍。由此可見,一旦變量被閉包捕獲后拨匆,外界使用者可以訪問被捕獲的變量值或引用姆涩,相當(dāng)于訪問了私有變量。

綜上所述惭每,閉包是函數(shù)式編程中實(shí)現(xiàn)名稱綁定的技術(shù)骨饿,直觀表現(xiàn)為函數(shù)嵌套以提升變量作用范圍,使原本壽命短暫的局部變量獲得了長生不老的能力台腥。只要被捕獲到的自由變量一直處于使用中宏赘,系統(tǒng)就不會回收其內(nèi)存空間。


閉包(Closure)又稱為Lambda表達(dá)式

閉包是由函數(shù)及其相關(guān)的引用環(huán)境共同組合而成的實(shí)體黎侈,即 “閉包 = 函數(shù) + 運(yùn)行(引用)環(huán)境”察署。因此有人又稱:”對象是附有行為的數(shù)據(jù),閉包是附有數(shù)據(jù)的行為“峻汉。

理解閉包首先需要了解函數(shù)的執(zhí)行環(huán)境(execution context)又稱為執(zhí)行上下文贴汪。

  • 函數(shù)是指執(zhí)行的代碼塊,由于自由變量被包含在代碼塊中休吠,因此這些自由變量以及它們引用的對象沒有被釋放扳埂。
  • 運(yùn)行環(huán)境是是指為自由變量提供綁定的計(jì)算環(huán)境,又稱為作用域瘤礁。

閉包包含了自由變量聂喇,自由變量是指沒有綁定到特定對象的變量。自由變量不是在當(dāng)前代碼塊內(nèi)或任何全局上下文中定義的蔚携,而是在定義代碼塊的環(huán)境中定義的局部變量希太。


閉包的體現(xiàn)形式在函數(shù)體內(nèi)返回另一個函數(shù)

閉包是指可以包含自由變量的代碼塊,自由變量特指沒有綁定到特定對象的變量酝蜒。

自由變量并不在代碼塊內(nèi)或全局上下文中定義誊辉,而會在定義代碼塊的環(huán)境中定義。

當(dāng)代碼塊所在的環(huán)境被外部調(diào)用時亡脑,代碼塊及其所引用的自由變量會構(gòu)成閉包堕澄。

要執(zhí)行的代碼塊會為自由變量提供綁定的計(jì)算環(huán)境(作用域),由于自由變量包含在代碼塊中霉咨,因此自由變量及其引用的對象不會被釋放掉蛙紫。

閉包

理解閉包最直觀的方法就是將閉包函數(shù)當(dāng)作一個類,一個閉包函數(shù)調(diào)用相當(dāng)于實(shí)例化類途戒,然后再從類的角度中區(qū)分那些是全局變量坑傅,那些是局部變量。實(shí)際上在Objective-C中閉包就是使用類來實(shí)現(xiàn)的喷斋。


閉包的價值在于可作為函數(shù)對象或匿名函數(shù)唁毒,對類型系統(tǒng)而言蒜茴,意味著不僅要表示數(shù)據(jù)還要表示代碼。

支持閉包的大多數(shù)語言都將函數(shù)作為第一公民(第一級對象)浆西,函數(shù)可以存儲到變量中作為參數(shù)傳遞給其他函數(shù)粉私,而且函數(shù)還可以被其他函數(shù)動態(tài)的創(chuàng)建和返回。


閉包一般是以匿名函數(shù)的形式出現(xiàn)近零,能夠動態(tài)且靈活的創(chuàng)建和傳遞诺核,由此體現(xiàn)出函數(shù)式編程的特點(diǎn)。

閉包的缺點(diǎn)在于函數(shù)中的變量會被保存在內(nèi)存中久信,因此內(nèi)存消耗很大窖杀,所以閉包不能濫用。


閉包是引用了自由變量的函數(shù)入篮,被引用的自由變量和函數(shù)會一同存在陈瘦,即使已經(jīng)離開了自由變量的環(huán)境也不會被釋放或刪除幌甘,在閉包中可以繼續(xù)使用這些自由變量潮售。

同一個函數(shù)與不同引用環(huán)境組合后可形成不同的實(shí)例

函數(shù)類型和結(jié)構(gòu)體一樣可以被實(shí)例化,由于函數(shù)本身不能存儲任何信息锅风,只有與引用環(huán)境結(jié)合后形成的閉包才具有記憶性酥诽。


Go語言不能在函數(shù)內(nèi)部聲明函數(shù),卻可以在函數(shù)體內(nèi)聲明函數(shù)類型的變量皱埠。

與普通變量聲明不同的是肮帐,Go語言不能在函數(shù)內(nèi)聲明另一個函數(shù),Go語言不支持在函數(shù)內(nèi)部顯式地嵌套定義函數(shù)边器,但卻可以定義匿名函數(shù)训枢。因此,Go語言不支持函數(shù)嵌套忘巧,比如在Go源文件中恒界,函數(shù)聲明都是出現(xiàn)在最外層的。

Go語言支持匿名函數(shù)砚嘴,匿名函數(shù)是沒有指定函數(shù)名稱的函數(shù)十酣。匿名函數(shù)相當(dāng)于一個內(nèi)聯(lián)語句或表達(dá)式,匿名函數(shù)的優(yōu)越性在于可以直接使用函數(shù)內(nèi)部的變量而無需事先聲明际长。

匿名函數(shù)是指不需要定義函數(shù)名的一種函數(shù)的實(shí)現(xiàn)方式耸采,匿名函數(shù)由一個不帶函數(shù)名的函數(shù)聲明和函數(shù)體構(gòu)成。

func(x, y int) int {
  return x + y
}

Go語言中函數(shù)也是一種數(shù)據(jù)類型工育,因此可以聲明函數(shù)類型的變量虾宇,使用函數(shù)類型的變量來接收函數(shù)。

Go語言中所有的函數(shù)都是值類型的如绸,既可以作為參數(shù)傳遞也可以作為返回值傳遞文留。

Go語言中可以將匿名函數(shù)賦值給變量

fn := func() int {
  
}

匿名函數(shù)可作為閉包

閉包是內(nèi)層函數(shù)引用了外層函數(shù)中的變量好唯,也就是所謂的引用自由變量的函數(shù)。

閉包的返回值也是一個函數(shù)

閉包只是在形式和表現(xiàn)上像函數(shù)燥翅,但實(shí)際并不是函數(shù)骑篙。因?yàn)楹瘮?shù)是編譯期靜態(tài)的概念,閉包是運(yùn)行期動態(tài)的概念森书。函數(shù)只是一段可執(zhí)行的代碼靶端,這些代碼在函數(shù)被定義后就確定了,也就是說編譯后就固化了凛膏,因此不會在執(zhí)行時發(fā)生變化杨名。每個函數(shù)在內(nèi)存中只會存在一份實(shí)例,所以說一個函數(shù)只是一個實(shí)例猖毫,只要得到函數(shù)的入口點(diǎn)即可執(zhí)行函數(shù)台谍。而閉包在運(yùn)行時可以擁有多個實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例吁断。

閉包與函數(shù)引用

所謂的引用環(huán)境是指在程序執(zhí)行過程中的某個點(diǎn)上所有處于活躍狀態(tài)的約束所組成的集合趁蕊,這里的約束是指一個變量的名字和其代表的對象之間的聯(lián)系。

函數(shù)式編程是一種編程模型仔役,將計(jì)算機(jī)運(yùn)算看作數(shù)學(xué)中函數(shù)的計(jì)算掷伙,以避免狀態(tài)以及變量的概念。

函數(shù)式編程中函數(shù)是一等公民(First-Class Value又兵,第一類對象)任柜,無需像命令式語言那樣借助函數(shù)指針,委托操作函數(shù)沛厨。因此函數(shù)可以作為另一個函數(shù)的返回值或參數(shù)宙地,也可以作為某個變量的值賦給某個變量。另外逆皮,函數(shù)可以嵌套定義宅粥,即在函數(shù)內(nèi)部可以定義另一個函數(shù)。由于有了嵌套函數(shù)這種結(jié)構(gòu)页屠,也就產(chǎn)生了閉包問題粹胯。

函數(shù)式編程中可以將函數(shù)作為參數(shù)傳遞,因此又稱之為高階函數(shù)辰企。在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中风纠,高階函數(shù)至少需要滿足下列兩個條件中的之一:

  • 接受一個或多個函數(shù)作為輸入
  • 輸出一個函數(shù)

為什么閉包需要將引用環(huán)境和函數(shù)組合起來呢?因?yàn)樵谥С智短鬃饔糜虻恼Z言中牢贸,有時不能簡單直接地確定函數(shù)的引用環(huán)境竹观。


閉包可以理解為定義在函數(shù)內(nèi)部的函數(shù),本質(zhì)上閉包是將函數(shù)內(nèi)部和外部連接的橋梁,是函數(shù)與其引用環(huán)境的組合臭增。


下面以計(jì)算斐波拉契數(shù)列為例懂酱,來分析下閉包。

非巴拉契數(shù)列是一個遞增的數(shù)列誊抛,形如1 1 2 3 5 8 13 21 34 55...列牺,即從第三個數(shù)開始后一個數(shù)字是前兩個數(shù)字之和。

非巴拉契數(shù)列

使用函數(shù)實(shí)現(xiàn)斐波拉契數(shù)列生成器

使用閉包可實(shí)現(xiàn)擁有自身狀態(tài)的函數(shù)


變量作用域

  • 全局變量:在main()函數(shù)執(zhí)行之前初始化拗窃,因此全局可見瞎领。
  • 局部變量:在函數(shù)內(nèi)或iffor等語句塊中有效随夸,使用后外部不可見九默。
  • 全局變量和局部變量同名時局部變量優(yōu)先生效

變量可見性

  • 包內(nèi)任何變量或函數(shù)都能訪問
  • 包外首字母大寫的可被訪問,首字母小寫的表示私有因此不能被外部調(diào)用宾毒。

變參函數(shù)

變參函數(shù)是指使用不同數(shù)量的參數(shù)調(diào)用的函數(shù)驼修,變參函數(shù)允許用戶在可變函數(shù)中傳遞0或多個參數(shù)。比如fmt.Print()可接受任意數(shù)量的參數(shù)诈铛。

變參函數(shù)聲明中乙各,最后一個參數(shù)的類型前帶有省略號...以表明該函數(shù)可以調(diào)用任意數(shù)量此類型的參數(shù)。

func fname(args, ...type) type {}

...type格式的類型只能作為函數(shù)的參數(shù)類型存在且必須是最后一個參數(shù)癌瘾,它是一個語法糖(syntax sugar)觅丰,即該語法對語言的功能并沒有影響饵溅,只是為了更方便開發(fā)人員使用妨退,使用語法糖能夠增加代碼的可讀性而從減少程序出錯的可能性。

從內(nèi)部實(shí)現(xiàn)來將蜕企,類型...type本質(zhì)上是一個數(shù)組切片咬荷,也就是[]type

func fn(args ...int){
    for k,v := range args {
        fmt.Println(k, v)
    }
}
fn(1, 2, 3)
func fn(args []int){
    for k,v := range args {
        fmt.Println(k, v)
    }
}

fn([]int{1, 2, 3})

由于...type可變參數(shù)本質(zhì)上是一個數(shù)組切片轻掩,因此為變參函數(shù)傳遞切片幸乒。

func fn(args ...int){
    for k,v := range args {
        fmt.Println(k, v)
    }
}

fn([]int{1, 2, 3}...)

slice := []int{1,2,3}
fn(slice...)

由于可變參數(shù)變量本身是一個包含函數(shù)參數(shù)的切片,若需要將含有可變參數(shù)的變量傳遞給下一個可變參數(shù)函數(shù)唇牧,可在傳遞時在可變參數(shù)后添加...罕扎,這樣即可將切片中的元素進(jìn)行傳遞,而無需傳遞可變參數(shù)變量本身丐重。

package main

import (
    "bytes"
    "fmt"
)

func concat(args ...string) string {
    var buf bytes.Buffer//定義字節(jié)緩沖用于快速連接字符串
    for _,v := range args {//遍歷可變參數(shù)列表腔召,類型為[]string
        buf.WriteString(v)//將遍歷出的字符串連續(xù)寫入字節(jié)數(shù)組
    }
    return buf.String()//將連接好的字節(jié)數(shù)組轉(zhuǎn)化為字符串
}

func print(args ...string){
    str := concat(args...)
    fmt.Println(str)
}

func main() {
    print("hell", "o")
}

可變參數(shù)使用...進(jìn)行傳遞與切片間使用append連接是同一種特性

可變參數(shù)不傳入任何值的時候,函數(shù)內(nèi)部會默認(rèn)為nil扮惦。

func fn(args ...int){
    fmt.Printf("args = %v, type = %T\n", args, args)
}
fn()//args = [], type = []int

使用interface{}空接口可指定任意類型的可變參數(shù)

func fn(args ...interface{}){
    fmt.Printf("args = %v, type = %T\n", args, args)
}
fn()//args = [], type = []interface {}

可變參數(shù)列表數(shù)量是不固定的臀蛛,傳入的參數(shù)是一個切片,如果需要獲得每個參數(shù)的具體值,可對可變參數(shù)變量進(jìn)行遍歷浊仆。

//快速拼接字符串
func concat(args ...string) string {
    var buf bytes.Buffer//定義字節(jié)緩沖用于快速連接字符串
    for _,v := range args {//遍歷可變參數(shù)列表客峭,類型為[]string
        buf.WriteString(v)//將遍歷出的字符串連續(xù)寫入字節(jié)數(shù)組
    }
    return buf.String()//將連接好的字節(jié)數(shù)組轉(zhuǎn)化為字符串
}
fmt.Printf("%s", concat("ham", "mer"))//hammer

延遲執(zhí)行

Go語言中defer語句會將其后面跟隨的語句進(jìn)行延遲處理

defer歸屬的函數(shù)即將返回時,將延遲處理的語句按defer的逆序進(jìn)行執(zhí)行抡柿。先被defer的語句最后被執(zhí)行舔琅,最后被defer的語句最先執(zhí)行。

當(dāng)多個defer行為被注冊時它們會議逆序執(zhí)行(類似棧洲劣,先進(jìn)后出搏明,LIFO)

func main() {
    fmt.Println("defer begin")
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("defer end")
}
defer begin
defer end
3
2
1

defer關(guān)鍵字類似Java或C#中的finally語句塊,用于釋放已被分配的資源闪檬,典型的例子是互斥解鎖或關(guān)閉文件星著。

處理業(yè)務(wù)或邏輯中涉及到成對操作時,比如文件的打開和關(guān)閉粗悯,請求的接受與回復(fù)虚循,加鎖與解鎖等,這些操作中样傍,最容易忽視的在每個函數(shù)退出時需釋放或關(guān)閉資源横缔。使用defer語句正好是在函數(shù)退出時執(zhí)行的語句,可方便地解決資源釋放等容易被忽視的問題衫哥。

使用defer延遲并發(fā)解鎖

當(dāng)在函數(shù)中并發(fā)讀寫map時為防止競態(tài)問題茎刚,使用sync.Mutex進(jìn)行加鎖。

package main

import "sync"

var (
    dict = make(map[string]string)//map默認(rèn)并非并發(fā)安全
    dictGuard sync.Mutex //為保證使用映射時的并發(fā)安全撤逢,使用互斥鎖膛锭。
)

//根據(jù)鍵讀取值
func read(key string) string{
    dictGuard.Lock()//對共享資源加鎖,使用互斥量加鎖蚊荣。
    defer dictGuard.Unlock()//對共享資源解鎖初狰,使用互斥量解鎖。延遲到函數(shù)結(jié)束時調(diào)用
    
    return dict[key]//獲取共享資源
}

使用defer延遲釋放文件句柄

//獲取文件大小
func filesize(filename string) int64 {
    //打開文件返回文件句柄
    fh, err := os.Open(filename)
    if err != nil {
        return 0
    }
    //延遲關(guān)閉文件
    defer fh.Close()
    
    //獲取文件狀態(tài)信息
    fileinfo, err := fh.Stat()
    if err != nil {
        return 0
    }
    //獲取文件大小
    filesize := fileinfo.Size()
    return filesize
}

遞歸函數(shù)

遞歸函數(shù)是指函數(shù)內(nèi)部調(diào)用函數(shù)自身的函數(shù)互例,構(gòu)成遞歸需要具備以下條件

  • 一個文件可以被拆分為多個子問題
  • 拆分前的原問題與拆分后的子問題除了數(shù)據(jù)規(guī)模不同奢入,處理問題的思路是一樣的。
  • 不能無限的調(diào)用本身媳叨,子問題需要擁有退出遞歸狀態(tài)的條件腥光。

遞歸函數(shù)編寫過程中,一定要有終止條件糊秆,否則函數(shù)會無限執(zhí)行下去直到內(nèi)存溢出武福。

例如:使用遞歸實(shí)現(xiàn)斐波那契數(shù)列

package main

func fib(n int) (result int) {
    if n < 2 {
        result  = 1
    }else{
        result = fib(n - 1) + fib(n - 2)
    }
    return
}

func main() {
    for i:=0; i<=10; i++{
        println(fib(i))
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扩然,隨后出現(xiàn)的幾起案子艘儒,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件界睁,死亡現(xiàn)場離奇詭異觉增,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翻斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門逾礁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人访惜,你說我怎么就攤上這事嘹履。” “怎么了债热?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵砾嫉,是天一觀的道長。 經(jīng)常有香客問我窒篱,道長焕刮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任墙杯,我火速辦了婚禮配并,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘高镐。我一直安慰自己溉旋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布嫉髓。 她就那樣靜靜地躺著观腊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岩喷。 梳的紋絲不亂的頭發(fā)上恕沫,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天监憎,我揣著相機(jī)與錄音纱意,去河邊找鬼。 笑死鲸阔,一個胖子當(dāng)著我的面吹牛偷霉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褐筛,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼类少,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渔扎?” 一聲冷哼從身側(cè)響起硫狞,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后残吩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體财忽,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年泣侮,在試婚紗的時候發(fā)現(xiàn)自己被綠了即彪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡活尊,死狀恐怖隶校,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛹锰,我是刑警寧澤深胳,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铜犬,受9級特大地震影響稠屠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翎苫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一权埠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煎谍,春花似錦攘蔽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至作岖,卻和暖如春唆垃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痘儡。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工辕万, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沉删。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓渐尿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矾瑰。 傳聞我的和親對象是個殘疾皇子砖茸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Go 和其他語言很大的一個不同就是函數(shù)可以返回多個返回值。 Go 中聲明的變量必須被使用殴穴,否則就會報(bào)錯凉夯,因?yàn)镚o認(rèn)...
    幣來幣往閱讀 451評論 0 0
  • 原創(chuàng)文章轉(zhuǎn)載請注明出處 今天看Martini文檔货葬,其功能列表提到完全兼容http.HandlerFunc接口,就去...
    咕咕鷄閱讀 23,500評論 3 32
  • 作者 | 冉小龍 審校 | Anonymitaet 編輯 | Susan 閱讀本文需要約 8 分鐘劲够。 - 導(dǎo)讀 -...
    StreamNative閱讀 410評論 0 0
  • 在學(xué)習(xí)如何編寫宝惰、部署 Go Function 之前,先向大家介紹一下 Go Function 的實(shí)現(xiàn)思路再沧。 在 一...
    wolf4j閱讀 681評論 0 0
  • 函數(shù)是組織好的尼夺、可重復(fù)使用的、用于執(zhí)行指定任務(wù)的代碼塊炒瘸。Go語言中支持函數(shù)淤堵、匿名函數(shù)和閉包,并且函數(shù)在Go語言中屬...
    Every_dawn閱讀 902評論 0 1